-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsearch.xml
More file actions
1544 lines (1363 loc) · 339 KB
/
search.xml
File metadata and controls
1544 lines (1363 loc) · 339 KB
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>幸福与孤独</title>
<url>/2009/02/14/%E5%B9%B8%E7%A6%8F%E4%B8%8E%E5%AD%A4%E7%8B%AC/</url>
<content><![CDATA[<h2 id="被懂得是一种幸福,等待被懂是一种孤独"><a href="#被懂得是一种幸福,等待被懂是一种孤独" class="headerlink" title="被懂得是一种幸福,等待被懂是一种孤独"></a>被懂得是一种幸福,等待被懂是一种孤独</h2><p>生命中总有些美丽的错误无法预料,就像总有些冷酷的分离无法避免一样</p>
<p>我意犹未尽的想你,以及有关你的所有</p>
<p>河畔的风,幽径的雨,冰封的河面</p>
<a id="more"></a>
<p>我醒来过后你温和的容颜</p>
<p>还有我在五楼窗台呼喊出的你的名字,一切做风雨逝</p>
<p>这些色彩游离的画面立刻构成了我的全部背景</p>
<p>你写在空气中的忧郁被夕阳带走</p>
<p>但我心如刀割,</p>
<p>我犹豫地、忐忑地、幼稚且执着地想向你暗示</p>
<p>几乎令你无可奈何</p>
<p>可你总是那么善良的鼓励着我向人生的目标去跋涉</p>
<p>但……</p>
<p>有一种爱叫放弃</p>
<p>有一种爱“挂着泪珠”,但很凄美……</p>
<p>有一种爱“叫放手”,放手不是放弃爱你……</p>
<p>每当夜晚来临的时候,思念就像潮水般涌向自己</p>
<p>当一切成了过眼云烟,当一切改变了模样</p>
<p>我是否还记得曾经的泪、曾经的笑、曾经的爱怜</p>
<p>是否要忘记过去的心</p>
<p>如果不是你的出现,就不会有我现在的失意</p>
<p>如果不是你,我不会日日夜夜为你牵挂</p>
<p>如果…… 如果……</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>生平小可之比</title>
<url>/2010/06/19/%E7%94%9F%E5%B9%B3%E5%B0%8F%E5%8F%AF%E4%B9%8B%E6%AF%94/</url>
<content><![CDATA[<h2 id="生平小可之比"><a href="#生平小可之比" class="headerlink" title="生平小可之比"></a>生平小可之比</h2><p>文字仿佛和我有着不朽的宿怨</p>
<p>在心头萦绕千年</p>
<p>现实里睡梦 狂纵不羁 八百贴壮志豪言</p>
<p>不是酒仙 却渴望饮一壶春水 吐半壁盛唐江山</p>
<p>睡梦里现实 顾影自怜 三五阙愁思款款</p>
<p>愿笑孔丘 解腰间环佩 赊七八斗酒馔</p>
<p>无人素手解连环</p>
<p>无人当歌怒拔剑</p>
<p>除了文字 任谁时光浩瀚</p>
<p>能追随我孤寂百年</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>想你却不能告诉你</title>
<url>/2010/05/14/%E6%83%B3%E4%BD%A0%E5%8D%B4%E4%B8%8D%E8%83%BD%E5%91%8A%E8%AF%89%E4%BD%A0/</url>
<content><![CDATA[<h2 id="想你却不能告诉你"><a href="#想你却不能告诉你" class="headerlink" title="想你却不能告诉你"></a>想你却不能告诉你</h2><p>我可以从意念,再写出、舞出一个世界。</p>
<p>深夜,对着孤灯,郊游在电脑前,我陷入深深的思念之中。此刻比任何时候都孤独,我怀疑自己的真实,怀疑现实的真实。好象被岁月开了一个玩笑,岁月对于人来说如同延伸的铁轨。</p>
<p>而现在的我如何来调整这个步伐。也许伤痛的心灵需要静静安抚,也许时间会将这一切尘封。</p>
<p>有些事情过去了就过去了,而有些事情过去了却过不去。不知怎样开始,难预料如何结束。生活有些时候带给我们的只是一场迷离的幻觉。繁华凋谢,荒草丛生。想要挣脱,却是被越缚越紧。冥冥中好像有一种强大的力量在控制着我们,我们逃不出他的掌心。追忆着旧日的时光,一些过去的人,一些过去的事,默默的念想,温暖冰冻的心灵。我们手中拥有的东西竟是那么少。情感脆弱,容易受伤也容易伤害别人。我们不停的追逐之后,发现自己离原定的目标越来越远。</p>
<p>有些人告别之后就不想再见,有些人告别着后依旧在默默的思念,期待者某一天的重逢。人与人分手便往往是永远。离别的站台,停靠的码头,生命的每一次告别都是一次蝉蜕的过程。慢慢的学会坚强。忍住泪,挥手说再见。</p>
<p>那时,我离开了你,我却忘了告诉你,你一直在我心中。</p>
<p>如果,有一种执着需要离开,那么离开就是对完美的补充,那种微妙的行程,就是那种神秘的距离,因为有了那神秘的距离而产生了美,因为这种美的距离的存在,才让爱达到了最高境界的完美。</p>
<p>一直这样想着,一直在放逐自己;一直的缠绵和追逐,而没有获得回头的机遇;一直不想放弃的朦胧,而没有确切的结果。</p>
<p>望着天空的星星,想尽力地去看清楚每一颗星星的模样和它们之间的距离。无论是在白天,还是在每一个黑夜,我都不能清晰的辨认它们的模样和距离。可是每一颗星星都安静地停留在属于它们的领地,而我的视线却是模糊的,它们就在我的模糊的视线里清晰的存在着。</p>
<p>想着,疑惑着,疑惑着,想着。</p>
<p>想着那个模糊的身影,疑惑着那个模糊的身影,对那个身影的思念是没有意义的思念,那个思念是属于另一个不属于我的世界里的思念,我应该离开那种被自己迷惑的身影,让自己从那种模糊的世界里游离出来,为自己找一片阳光,让自己的呼吸暖和起来。</p>
<p>那个身影的离开,是彻底的离开,是善意的离开,是清楚的离开,就是为了追求属于身影的完美,也是为了让我也去追求属于我的完美,原来那个身影的离开是那种和谐的完美。</p>
<p>离开,是对完美的补充。</p>
<p>留下一段文字,一些伤感的故事。生命又走过了寂寞的一程。回首往事,恍然入梦。是我们的生命脆弱,容易受伤,还是这个世界太过坚硬。</p>
<p>某一天我们熟悉。某一天我们陌生。</p>
<p>我们走过了一个繁华的季节。</p>
<p>其实我们都是熟悉的陌生人。只希望我们每个人都多一些宽容,多一些真诚,多一些爱!</p>
<p>人生充满了遗憾。有时候,遗憾也未尝不是一种美,只是,这美是要付出昂贵的代价的,常常会心痛,常常怀念,却永远深埋在那里,这一种爱是刻骨铭心的,无论怎样努力也无法从心头驱散。</p>
<p>一切是不是错?一切是不是很荒唐?我始终没想明白。</p>
<p>想你,却不能告诉你……</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>近期的烦躁</title>
<url>/2010/06/19/%E8%BF%91%E6%9C%9F%E7%9A%84%E7%83%A6%E8%BA%81/</url>
<content><![CDATA[<h2 id="我可以说是胡思乱想吗"><a href="#我可以说是胡思乱想吗" class="headerlink" title="我可以说是胡思乱想吗"></a>我可以说是胡思乱想吗</h2><p>自从发现自己已历经二十多个春秋时,心便开始隐隐不安了。</p>
<p>总是对回忆充满怀念,总是会浮想联翩。</p>
<p>每天的校园生活宛如行尸走肉</p>
<p>我活在自己编织的世界里</p>
<p>一个用幻想编制的梦……</p>
<hr>
<p>总站在风里的人,会感觉肌肤干涩。</p>
<p>总站在雨里的人,会感觉眼眶湿润。</p>
<p>总站在时光里的人,会感觉内心苍老。</p>
<hr>
<p>关于苍老的问题,每次提及,总会让我发笑。</p>
<p>它的范围太过宽泛了,轻松的跨越了几乎各个年龄梯段。</p>
<p>它的内涵太过深邃了,在思索它的所在时,我们不知不觉已被打上时光的印记。</p>
<hr>
<p>才发现,在时光面前我无能为力,只能眼睁睁的看着它一点一滴的消逝。</p>
<p>都说双鱼座的人很感性、爱幻想,大概我就是传说中的那只鱼。</p>
<hr>
<p>关于生活,我的短期理想是在烦躁过后可以变的从容。</p>
<p>如果生活是一种态度,</p>
<p>就像幸福是一种感受,骄傲其实是一种武器,回忆是对现实的嘲弄一样,</p>
<p>那么我们除了回不去过去,迷惘在现在的理由何在呢?</p>
<p>陷入思考中……</p>
<hr>
<p>STOP STOP</p>
<p>我的经验告诉我要STOP</p>
<p>陷入思考的结果肯定就是引发烦恼了</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>那些年</title>
<url>/2012/05/27/%E9%82%A3%E4%BA%9B%E5%B9%B4/</url>
<content><![CDATA[<h2 id="年少的那一年,青春的那一年,还是成长的那一年?"><a href="#年少的那一年,青春的那一年,还是成长的那一年?" class="headerlink" title="年少的那一年,青春的那一年,还是成长的那一年?"></a>年少的那一年,青春的那一年,还是成长的那一年?</h2><h3 id="那一年,毕业"><a href="#那一年,毕业" class="headerlink" title="那一年,毕业"></a>那一年,毕业</h3><p>二手的自行车再也载不动爱情</p>
<p>笔记本电脑代替PC,终点不再是游戏性</p>
<p>offer在纷飞,朋友要远行</p>
<p>散伙饭吃尽了四年的愁绪</p>
<p>青春,被西装领带包裹着</p>
<p>离开校园的那段路,走走停停……</p>
<h3 id="那一年,菜鸟"><a href="#那一年,菜鸟" class="headerlink" title="那一年,菜鸟"></a>那一年,菜鸟</h3><p>在钢筋水泥的职场丛林</p>
<p>我只是个雏儿</p>
<p>被调教、被训斥、被肯定、被鼓励</p>
<p>摸爬滚打的岁月里</p>
<p>稚嫩的肩膀上柔软的翎毛开始发亮</p>
<p>虽然孤单但日渐充实的人生……</p>
<h3 id="那一年,成蝶"><a href="#那一年,成蝶" class="headerlink" title="那一年,成蝶"></a>那一年,成蝶</h3><p>昔日的梦想变成每月的工资单</p>
<p>也许,只要一个卫生间的钱</p>
<p>你就可以把世界走遍</p>
<p>可是我拥有了成长,却走不出时间……</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>SpringMVC RequestMapping</title>
<url>/2016/07/04/SpringMVC-RequestMapping/</url>
<content><![CDATA[<p>Quick note about request mapping annotations in SpringMVC controller</p>
<a id="more"></a>
<h2 id="RequestMapping"><a href="#RequestMapping" class="headerlink" title="@RequestMapping"></a>@RequestMapping</h2><p>Annotation for <strong>mapping web requests onto specific handler classes and/or handler methods</strong>. It means <code>DispatcherServlet</code> intercepts the request, then it switches request to the corresponding method determined by @RequestMapping.</p>
<ol>
<li><code>@RequestMapping("path")</code> on class means all handling methods on this controller are relative to the given path.</li>
<li><code>@RequestMapping("path")</code> on method means mapping requests which match given path to this method</li>
</ol>
<h3 id="Properties"><a href="#Properties" class="headerlink" title="Properties"></a>Properties</h3><ul>
<li><p><code>value</code> indicates url to map. If no other properties, we could use its simplified form <code>@RequestMapping("path")</code>.</p>
</li>
<li><p><code>method</code> indicates HTTP methods. It will support all methods if not specified .</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">method = RquestMethod.GET</span><br><span class="line">method = {RquestMethod.GET, RquestMethod.POST}</span><br></pre></td></tr></table></figure>
<ul>
<li><code>consumes</code> indicates Content-Type of the mapped request. A request will be mapped only when its Content-Type matches it.</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">consumes = <span class="string">"application/json"</span></span><br><span class="line">consumes = {<span class="string">"application/json"</span>, <span class="string">"text/html"</span>}</span><br></pre></td></tr></table></figure>
<ul>
<li><code>produces</code> indicates the producible media types of the mapped request, <strong>a request will be mapped only when Accept matches it</strong>.</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">produces = <span class="string">"application/json"</span></span><br><span class="line">produces = {<span class="string">"application/json"</span>, <span class="string">"charset=UTF-8"</span>}</span><br></pre></td></tr></table></figure>
<ul>
<li><code>headers</code> indicates only the requests having these headers can be mapped.</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">headers = <span class="string">"content-type=text/*"</span></span><br></pre></td></tr></table></figure>
<ul>
<li><code>params</code> indicates only the requests having these parameters can be mapped. We could also add <code>!=</code> pr <code>==</code> to add conditions.</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// myParam exists and its value is myValue</span></span><br><span class="line">params=<span class="string">"myParam = myValue"</span> </span><br><span class="line"></span><br><span class="line"><span class="comment">// myParamA exists and its value is myValueA. // myParamB exists and its value is not myValueB</span></span><br><span class="line">params = {<span class="string">"myParamA = myValueA"</span>, <span class="string">"myParamB != myValueB"</span>}</span><br><span class="line"></span><br><span class="line"><span class="comment">// myParamA exists</span></span><br><span class="line">params = <span class="string">"myParamA"</span> </span><br><span class="line"></span><br><span class="line"><span class="comment">// myParamA exists and myParamB does not exits</span></span><br><span class="line">params = {<span class="string">"myParamA"</span>, <span class="string">"!myParamB"</span>} </span><br></pre></td></tr></table></figure>
<h3 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping("/users")</span> </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestController</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Handler all /users GET request</span></span><br><span class="line"> <span class="meta">@RequestMapping(method = RequestMethod.GET)</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">functionA</span><span class="params">()</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Handler all /users/new POST request</span></span><br><span class="line"> <span class="meta">@RequestMapping(value="/new", method = RequestMethod.POST)</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">functionC</span><span class="params">()</span> </span>{</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="Ant-style-path-patterns-to-indicate-map-url"><a href="#Ant-style-path-patterns-to-indicate-map-url" class="headerlink" title="Ant-style path patterns to indicate map url."></a>Ant-style path patterns to indicate map url.</h3><ul>
<li><code>/user/*/login</code> matches /user/aaa/login</li>
<li><code>/user/**/login</code> matches /user/login or /user/aaa/login or /user/aaa/bbb/login</li>
<li><code>/user/login??</code> matches /user/loginAA or /user/loginBB</li>
<li><code>/user/{userId}</code> matches /user/123 or /user/342 (using <code>@PathVariable</code> to indicate userID)</li>
</ul>
<h2 id="PathVariable"><a href="#PathVariable" class="headerlink" title="@PathVariable"></a>@PathVariable</h2><p><strong>It can be used on a method argument to bind it to the value of a URI template variable</strong>. The argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a <code>TypeMismatchException</code> if it fails to do.</p>
<blockquote>
<p>If we do not specify the url placeholder name like <code>@PathVariable('name')</code>, we must keep method parameter name same as url placeholder.</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping("/users")</span> </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Handler all /users/{id} GET request</span></span><br><span class="line"> <span class="meta">@RequestMapping(value="/{id}", method = RequestMethod.GET)</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">functionA</span><span class="params">(<span class="meta">@PathVariable</span> <span class="keyword">int</span> id)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Or if you want another parameter name</span></span><br><span class="line"> <span class="comment">//@RequestMapping(value="/{id}", method = RequestMethod.GET)</span></span><br><span class="line"> <span class="comment">//public void functionB(@PathVariable("id") int anotherName) {</span></span><br><span class="line"> <span class="comment">// // ToDo</span></span><br><span class="line"> <span class="comment">//}</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>A more complex example:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping("/users/{userId}")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping("/book/{bookId}")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">(<span class="meta">@PathVariable</span> String userId, <span class="meta">@PathVariable</span> String bookId)</span> </span>{</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>
<h2 id="RequestParam"><a href="#RequestParam" class="headerlink" title="@RequestParam"></a>@RequestParam</h2><p>It is used to <strong>bind request parameters to a method parameter in the controller</strong>. Do not mix it with <code>@PathVariable</code> which is used to obtain placeholders from the uri only.</p>
<p>As usual, we do it like this <code>request.getParameter("name")</code>, now with annotation:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(value="/user/{userId}/books", method = RequestMethod.GET)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="meta">@PathVariable("userId")</span> <span class="keyword">int</span> user,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="meta">@RequestParam(value = "date", required = false)</span> Date dateOrNull)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>It has three properties:</p>
<ol>
<li><code>value</code> is the key to get value from request</li>
<li><code>required</code> is to indicate whether request must have this parameter. By default is true.</li>
<li><code>defaultValue</code> is to set default value when parameter in request does not exist.</li>
</ol>
<blockquote>
<p>Same as <code>@PathVariable('name')</code>. If we do not specify <code>value</code>. We must need to keep method parameter name the same as key.</p>
</blockquote>
<h2 id="CookieValue"><a href="#CookieValue" class="headerlink" title="@CookieValue"></a>@CookieValue</h2><p>Same as <code>@RequestParam</code> but bind cookie values to a method parameter. It also has three properties <code>value</code>, <code>required</code> and <code>defaultValue</code> which are also the same </p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(value="/user", method = RequestMethod.GET)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">(<span class="meta">@CookieValue("foo")</span> String valueFromCookie)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="Ref"><a href="#Ref" class="headerlink" title="Ref"></a>Ref</h2><ul>
<li><a href="http://jiuye.jikexueyuan.com/play?id=2239&class_id=36">极客学院-常用注解类</a></li>
<li><a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html">Annotation Type RequestMapping</a></li>
</ul>
]]></content>
<categories>
<category>Spring</category>
</categories>
<tags>
<tag>Spring</tag>
<tag>SpringMVC</tag>
</tags>
</entry>
<entry>
<title>忆高考誓言</title>
<url>/2010/04/28/%E5%BF%86%E9%AB%98%E8%80%83%E8%AA%93%E8%A8%80/</url>
<content><![CDATA[<h2 id="备考宣言"><a href="#备考宣言" class="headerlink" title="备考宣言"></a>备考宣言</h2><blockquote>
<p>今日青云志,明朝题名时</p>
<p>敛翼立千仞,凝眸视八方</p>
<p>失意休气馁,得势莫猖狂</p>
<p>书山高,勤奋定有通天道</p>
<p>学海深,顽强能启探宝门</p>
<p>失败不是我要的归宿</p>
<p>状语亦不能解决问题</p>
<p>疾风知劲草,烈火见真金</p>
<p>只有行动方可表明自己的心迹</p>
<p>只有事实才能证明自己的能力</p>
<p>任凭艰难险阻</p>
<p>挡不住我勇往直前</p>
<p>纵横天高舒豪气</p>
<p>叱咤风云看今朝</p>
</blockquote>
<h2 id="临考誓言"><a href="#临考誓言" class="headerlink" title="临考誓言"></a>临考誓言</h2><blockquote>
<p>金戈作笔,岁月为马</p>
<p>时光荏苒,依稀之间早已过了二倚门</p>
<p>回首,那淅沥般如雨似雾的年龄</p>
<p>离开鸟巢,展翅高飞</p>
<p>去拼搏那变幻的云,傲视那短浅寡问的小生命</p>
<p>这是雏鹰的选择</p>
<p>冲击马棚,征战沙场</p>
<p>驰骋于硝烟弥漫的拼杀之中</p>
<p>感受灰飞烟灭的豪迈</p>
<p>这是骏马的选择</p>
<p>明知前路多崎岖,偏要追求风和雨</p>
<p>这是一个临考生——我的选择</p>
<p>十年寒窗读书苦,一朝题名幸福笑</p>
<p>辛勤积蓄待两日,战场拼搏两小时</p>
<p>千辛万苦迎面冲,希望之道条条通</p>
<p>从容自若倾心待,成功之门必然开</p>
</blockquote>
]]></content>
<categories>
<category>诗词</category>
</categories>
<tags>
<tag>诗词</tag>
</tags>
</entry>
<entry>
<title>看山不是山·看水不是水的境界</title>
<url>/2010/07/30/%E7%9C%8B%E5%B1%B1%E4%B8%8D%E6%98%AF%E5%B1%B1-%E7%9C%8B%E6%B0%B4%E4%B8%8D%E6%98%AF%E6%B0%B4%E7%9A%84%E5%A2%83%E7%95%8C/</url>
<content><![CDATA[<h2 id="“看山不是山·看水不是水”的境界"><a href="#“看山不是山·看水不是水”的境界" class="headerlink" title="“看山不是山·看水不是水”的境界"></a>“看山不是山·看水不是水”的境界</h2><p>对于我来说。</p>
<p>未来,仅仅是用如果来诠释的种种假设。它在,却也不在。</p>
<p>过往,不过是早已付之湮灭的结局。它在,却更不在。</p>
<p>我在消逝的过往中向虚无的未来里缓缓踱去,我在,却真不在。</p>
<hr>
<p>我告诉寂夜,有人,回来了。</p>
<p>他,叫做忧伤——我的另一片影子。</p>
<p>正午,他蜷缩在角落里躲避光雨,微小却愈发清晰。</p>
<p>子夜,他徘徊在星斗间聆听籁寂,模糊而更加高大。</p>
<hr>
<p>街道是陌生的,可昏黄的晕圈里,我与脚下的影子熟稔。</p>
<p>建筑是陌生的,可破旧的墙壁上,伤疤般曝露在空气中的殷红色砖块,唤醒了我心底隐隐的刺痛。</p>
<p>人群是陌生的,可那些闯入我视线中的面孔,与暂别的那所城市中遇见的,同样陌生。</p>
<p>夜色藏匿不了的,不仅仅是城市的繁华,还有我心底的孤影。</p>
<p>音乐安抚不了的,不仅仅是灼烫的热浪,还有我思绪的波涛。</p>
<hr>
<p>上一个已故的十年.</p>
<p>我一年疯.两年狂.三四初拈言律.五六信手成章.劳七八九岁月.撰十载情殇.</p>
<p>下一个将至的十年</p>
<p>等待支离破碎的花事.卷土重来</p>
<hr>
<p>忽然发现.自己还是在那个“看山不是山,看水不是水”的境界里.转来转去找不到出口</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>诗词新编</title>
<url>/2009/03/06/%E8%AF%97%E8%AF%8D%E6%96%B0%E7%BC%96/</url>
<content><![CDATA[<h2 id="一剪梅·处女"><a href="#一剪梅·处女" class="headerlink" title="一剪梅·处女"></a>一剪梅·处女</h2><blockquote>
<p>少行多休eat足</p>
<p>盘膝于床</p>
<p>袒胸露乳</p>
<p>梦中谁在对天吼</p>
<p>醒来看时</p>
<p>酣睡正熟</p>
<p>电脑厕所忙不停</p>
<p>一指速敲</p>
<p>两耳似聋</p>
<p>网站广告赚钱途</p>
<p>才下QQ</p>
<p>钱上手头</p>
</blockquote>
<p><em>PS.在处女生日来临之际,特作此诗!祝处女生日快乐!</em></p>
<h2 id="如梦令"><a href="#如梦令" class="headerlink" title="如梦令"></a>如梦令</h2><blockquote>
<p>常记公署日暮</p>
<p>约会不知归路</p>
<p>兴尽晚回校</p>
<p>误入走廊深处</p>
<p>LOVE LOVE</p>
<p>让我一阵麻木</p>
</blockquote>
<h2 id="如梦令-1"><a href="#如梦令-1" class="headerlink" title="如梦令"></a>如梦令</h2><blockquote>
<p>校园雨疏风骤</p>
<p>学费岂可乱收</p>
<p>试问收款人</p>
<p>却道以后研究</p>
<p>知否 知否</p>
<p>应是你肥我瘦</p>
</blockquote>
<h2 id="山坡羊·爱情之路"><a href="#山坡羊·爱情之路" class="headerlink" title="山坡羊·爱情之路"></a>山坡羊·爱情之路</h2><blockquote>
<p>友情如雨</p>
<p>爱情如雾</p>
<p>相恋三年坎坷路</p>
<p>忘情人</p>
<p>意踌躇</p>
<p>伤心曾经走过的路</p>
<p>山盟海誓都作了土</p>
<p>聚 我也苦</p>
<p>散 我也苦</p>
</blockquote>
<h2 id="诉衷情"><a href="#诉衷情" class="headerlink" title="诉衷情"></a>诉衷情</h2><blockquote>
<p>当年梦里相追求</p>
<p>也曾有风流</p>
<p>爱情湮灭何处</p>
<p>尘暗旧情路</p>
<p>愿未遂 心已碎 泪空流</p>
<p>此生谁料</p>
<p>心系旧情 身老孤愁</p>
</blockquote>
<h2 id="学子吟"><a href="#学子吟" class="headerlink" title="学子吟"></a>学子吟</h2><blockquote>
<p>常常整夜不眠,深夜挑灯读卷。</p>
<p>个个犹如熊猫,实在不忍观看。</p>
<p>只为升学考试,被迫埋头苦干。</p>
<p>夜半三更惊起,题海腋窝打转。</p>
<p>无奈天生薄命,分数实在凄惨。</p>
<p>举杯仰天长叹,血誓来年再战。</p>
</blockquote>
]]></content>
<categories>
<category>诗词</category>
</categories>
<tags>
<tag>诗词</tag>
</tags>
</entry>
<entry>
<title>晴晖云影·盘旋在谁的波澜不惊</title>
<url>/2010/07/24/%E6%99%B4%E6%99%96%E4%BA%91%E5%BD%B1-%E7%9B%98%E6%97%8B%E5%9C%A8%E8%B0%81%E7%9A%84%E6%B3%A2%E6%BE%9C%E4%B8%8D%E6%83%8A/</url>
<content><![CDATA[<h2 id="晴晖云影·盘旋在谁的波澜不惊"><a href="#晴晖云影·盘旋在谁的波澜不惊" class="headerlink" title="晴晖云影·盘旋在谁的波澜不惊"></a>晴晖云影·盘旋在谁的波澜不惊</h2><p>夜色在寂静上空盘旋,愈发冷清。</p>
<p>当跛脚行走在旷寂里的孤独,遭遇埋头叹息的忧愁。</p>
<p>无人的陌生十字路口,他们席地而坐,细数彼此眸子中黯淡的星光。</p>
<p>这一瞬,时光倒流,回忆穿越了瞳孔,洄溯到曾经草长莺飞的荒芜地带。</p>
<p>年少时的情爱,迷如阑珊,幻如煌炎,仿佛一场不愿醒的酣梦,缭绕着栀子淡淡的芬芳。</p>
<p>也许,用冗长的文字来记叙短暂的梦寐,是我在每个无眠的长夜里,可以收获的唯一的慰藉。</p>
<hr>
<p>日子,就像一支不断变奏的钢琴曲。</p>
<p>时而匆匆,时而缓缓,任我不疾不徐的一一作别。</p>
<p>间或,一小节音符在仔细聆听前,已耳畔悄悄划过。</p>
<p>可以停顿,却不必停留;可以慨叹,却不需惆怅。</p>
<p>因为,只有一些人,一些事以残音的形式滞留于流年。</p>
<p>我才知晓,如何很久的停留在你世界的边缘,弹一首惆怅的曲子。</p>
<hr>
<p>越陌度阡,任我能飞越过阻隔你我的空间。</p>
<p>却始终留不住昔日,与你谈斗笔墨的时间。</p>
<p>也许,我就是那薄如蝉翼的晴晖和云影。</p>
<p>悄无声息的在你肩膀降落,又悄无声息的在你肩膀坠下。</p>
<p>但我仍愿在每一天的一明一暗里,为你弹奏饱经烟尘风沙的千年不变。</p>
<hr>
<p>光明隐匿在黑暗背后,才愈加灿亮。</p>
<p>金子埋藏在泥土深处,才愈加珍贵。</p>
<p>寂夜里,我在文字的渊邃里穿凿。</p>
<p>只为在下一个晴日,许你,最华美的佩饰。</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>在眼前却在天边</title>
<url>/2010/07/15/%E5%9C%A8%E7%9C%BC%E5%89%8D%E5%8D%B4%E5%9C%A8%E5%A4%A9%E8%BE%B9/</url>
<content><![CDATA[<h2 id="在眼前却在天边"><a href="#在眼前却在天边" class="headerlink" title="在眼前却在天边"></a>在眼前却在天边</h2><p>不知从何时起,我开始习惯沉默了。</p>
<p>对身边的事物有了一种逃避感,开始以为是低调,现在发现那是淡然。</p>
<p>好像整个世界都是灰色的,没有任何色彩华丽。</p>
<p>那些熟悉的面孔,那些熟悉的声音,渐渐地被隐匿起来。</p>
<p>一下子孤独了很多,一下子苍老了很多。</p>
<hr>
<p>他们说我活的平淡了、平凡了,可我却怎么不觉得是。</p>
<p>为什么平淡中有很多不舍?为什么平凡中有些许遗憾?</p>
<p>我时刻都在探测自己的内心,苦思冥想,到底想要什么,到底该怎么做。</p>
<p>很久以前,自己也是那样无忧无虑、阳光灿烂。</p>
<p>曾几何时,按耐不住那颗好奇的心,无所畏惧的去追求自己的热爱。</p>
<p>而如今,除了孤独、寂寞之外我还剩下些什么?</p>
<p>或许还有那么一丝不舍,或许还有那么一丝遗憾,或许永远也只能仅存那么一点点留恋!</p>
<p>这个城市给了我太多的伤心,就像它的名字一样冰冷无情。</p>
<p>快乐的时光永远那么短暂,却印下了我们无法遗忘的点点滴滴,那么与众不同。</p>
<hr>
<p>不知从何时起,我的视线渐渐模糊,再也看不清你的容颜。</p>
<p>我心里所有的空间,只能容下一个你,满满的,无法消失更无法丢弃。</p>
<p>可我却再也感觉不到你,仿佛就在眼前,又好像远在天边。</p>
<p>很久很久没有你的声音,犹如失去了灵魂一样,茫然不知所错。</p>
<p>曾经,我知道你很在乎我,至少你心里有我,从来不曾怀疑过这种感觉。</p>
<p>如今,我真的不确定你是否在乎我,是否把我当做一个重要的人。</p>
<p>甚至,感觉你的记忆里从来没有过我的身影,一切只不过是我的想象。</p>
<hr>
<p>如果我们之间真的有过那么一丝情感,那么这种感情算是什么?</p>
<p>如果我们之间真的有过那么一次激情,那么这种行为算是什么?</p>
<p>如果说这些只是内心深处的好奇,那么清醒之后彼此如何面对?</p>
<p>我想应该是当做不曾发生的忘记,而不是一走了之的逃避。</p>
<p>我从来都不敢去回想我们之间的痛苦,因为我怕得到的结果是欺骗,因为我怕失去你。</p>
<p>其实,现在我们已经是最熟悉的陌生人了,根本谈不上什么失去。</p>
<p>无所谓了,反正注定我是孤独、寂寞,也不在乎这些了。</p>
<p>在我心里,我还是我,你还是你,只不过我们相隔的太远罢了。</p>
<p>那梦幻般的脑海和寂寞的心一直也永远被你占据着。</p>
<p>在眼前却在天边。。。</p>
]]></content>
<categories>
<category>文字随笔</category>
</categories>
<tags>
<tag>文字随笔</tag>
</tags>
</entry>
<entry>
<title>Nginx配置Google Fonts反向代理</title>
<url>/2016/09/19/google-fonts-proxy/</url>
<content><![CDATA[<p>使用Nginx反向代理谷歌字体库,可缓存加快访问速度。</p>
<a id="more"></a>
<h2 id="谷歌字体库"><a href="#谷歌字体库" class="headerlink" title="谷歌字体库"></a>谷歌字体库</h2><p>比如以下地址,虽然不再被墙,但访问有时不太稳定,加载速度慢。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic,700italic</span><br></pre></td></tr></table></figure>
<h2 id="国内镜像"><a href="#国内镜像" class="headerlink" title="国内镜像"></a>国内镜像</h2><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">http://fonts.useso.com/css?family=Source+Sans+Pro:300,400,600,300italic,400italic,600italic</span><br><span class="line">360的,目前已不再维护</span><br><span class="line"></span><br><span class="line">http://fonts.gmirror.org/css?family=Lato:400,700|Roboto+Slab:400,700|Inconsolata:400,700</span><br></pre></td></tr></table></figure>
<h3 id="使用过程的一些问题"><a href="#使用过程的一些问题" class="headerlink" title="使用过程的一些问题"></a>使用过程的一些问题</h3><ol>
<li>大部分镜像不支持https</li>
<li>打开页面的速度寄托在别人的服务器</li>
<li>特殊网络环境可能无法访问</li>
</ol>
<h2 id="搭建反向代理服务"><a href="#搭建反向代理服务" class="headerlink" title="搭建反向代理服务"></a>搭建反向代理服务</h2><p>准备以下应用环境,局域网/公网环境都可以,根据自己需要。</p>
<ol>
<li>可访问的IP或域名(例如<code>fonts.yourdomain.com</code>)</li>
<li>机器环境 linux + nginx</li>
</ol>
<p>安装nginx及第三方模块等就忽略了,直接贴上配置文件。</p>
<h3 id="修改nginx缓存"><a href="#修改nginx缓存" class="headerlink" title="修改nginx缓存"></a>修改nginx缓存</h3><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Nginx Cache Settings</span></span><br><span class="line"><span class="attribute">proxy_temp_file_write_size</span> <span class="number">128k</span>;</span><br><span class="line"><span class="attribute">proxy_temp_path</span> /data/nginx_cache/proxy_temp;</span><br><span class="line"><span class="attribute">proxy_cache_path</span> /data/nginx_cache/proxy_cache levels=<span class="number">1</span>:<span class="number">2</span> keys_zone=cache_one:<span class="number">200m</span> inactive=<span class="number">30d</span> max_size=<span class="number">30g</span>;</span><br></pre></td></tr></table></figure>
<p>其中,<code>/data/nginx_cache/proxy_temp</code>和<code>/data/nginx_cache/proxy_cache</code>是自定义的目录,如不存在请创建目录。</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">mkdir -p /data/nginx_cache/proxy_temp</span><br><span class="line">mkdir -p /data/nginx_cache/proxy_cache</span><br></pre></td></tr></table></figure>
<p>缓存时间为30天,里面各参数根据自己需要进行修改。</p>
<h3 id="反向代理配置"><a href="#反向代理配置" class="headerlink" title="反向代理配置"></a>反向代理配置</h3><p>fonts.yourdomain.com.conf</p>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">upstream</span> google {</span><br><span class="line"> <span class="attribute">server</span> fonts.googleapis.com:<span class="number">443</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="attribute">upstream</span> gstatic {</span><br><span class="line"> <span class="attribute">server</span> fonts.gstatic.com:<span class="number">443</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> {</span><br><span class="line"> <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">server_name</span> fonts.yourdomain.com;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">resolver</span> <span class="number">8.8.8.8</span>;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">access_log</span> /data/logs/nginx/fonts.yourdomain.com.access.log main;</span><br><span class="line"> <span class="attribute">error_log</span> /data/logs/nginx/fonts.yourdomain.com.<span class="literal">error</span>.log <span class="literal">info</span>;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> /css {</span><br><span class="line"> <span class="attribute">sub_filter</span> <span class="string">'fonts.gstatic.com'</span> <span class="string">'fonts.yourdomain.com'</span>;</span><br><span class="line"> <span class="attribute">sub_filter_once</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">sub_filter_types</span> text/css;</span><br><span class="line"> <span class="attribute">proxy_pass_header</span> Server;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Host fonts.googleapis.com;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Accept-Encoding <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Real-IP $remote_addr;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Scheme $scheme;</span><br><span class="line"> <span class="attribute">proxy_pass</span> https://google;</span><br><span class="line"> <span class="attribute">proxy_cache</span> cache_one;</span><br><span class="line"> <span class="attribute">proxy_cache_valid</span> <span class="number">200</span> <span class="number">304</span> <span class="number">365d</span>;</span><br><span class="line"> <span class="attribute">proxy_cache_key</span> $host$uri$is_args$args;</span><br><span class="line"> <span class="attribute">expires</span> max;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> /icon {</span><br><span class="line"> <span class="attribute">sub_filter</span> <span class="string">'fonts.gstatic.com'</span> <span class="string">'fonts.yourdomain.com'</span>;</span><br><span class="line"> <span class="attribute">sub_filter_once</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">sub_filter_types</span> text/css;</span><br><span class="line"> <span class="attribute">proxy_pass_header</span> Server;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Host fonts.googleapis.com;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Accept-Encoding <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Real-IP $remote_addr;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Scheme $scheme;</span><br><span class="line"> <span class="attribute">proxy_pass</span> https://google;</span><br><span class="line"> <span class="attribute">proxy_cache</span> cache_one;</span><br><span class="line"> <span class="attribute">proxy_cache_valid</span> <span class="number">200</span> <span class="number">304</span> <span class="number">365d</span>;</span><br><span class="line"> <span class="attribute">proxy_cache_key</span> $host$uri$is_args$args;</span><br><span class="line"> <span class="attribute">expires</span> max;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> / {</span><br><span class="line"> <span class="attribute">proxy_pass_header</span> Server;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Host fonts.gstatic.com;</span><br><span class="line"> <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Real-IP $remote_addr;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Scheme $scheme;</span><br><span class="line"> <span class="attribute">proxy_pass</span> https://gstatic;</span><br><span class="line"> <span class="attribute">proxy_cache</span> cache_one;</span><br><span class="line"> <span class="attribute">proxy_cache_valid</span> <span class="number">200</span> <span class="number">304</span> <span class="number">365d</span>;</span><br><span class="line"> <span class="attribute">proxy_cache_key</span> $host$uri$is_args$args;</span><br><span class="line"> <span class="attribute">expires</span> max;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> {</span><br><span class="line"> <span class="attribute">listen</span> <span class="number">443</span> ssl;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">ssl</span> <span class="literal">on</span>;</span><br><span class="line"> <span class="attribute">ssl_certificate</span> /root/ssl/css.crt;</span><br><span class="line"> <span class="attribute">ssl_certificate_key</span> /root/ssl/css.key;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">ssl_prefer_server_ciphers</span> <span class="literal">on</span>;</span><br><span class="line"> <span class="attribute">ssl_dhparam</span> /etc/ssl/certs/dhparam.pem;</span><br><span class="line"> <span class="attribute">ssl_protocols</span> TLSv1 TLSv1.<span class="number">1</span> TLSv1.<span class="number">2</span>;</span><br><span class="line"> <span class="attribute">ssl_ciphers</span> <span class="string">"EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4"</span>;</span><br><span class="line"> <span class="attribute">keepalive_timeout</span> <span class="number">70</span>;</span><br><span class="line"> <span class="attribute">ssl_session_cache</span> shared:SSL:<span class="number">10m</span>;</span><br><span class="line"> <span class="attribute">ssl_session_timeout</span> <span class="number">10m</span>;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">server_name</span> fonts.yourdomain.com;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">resolver</span> <span class="number">8.8.8.8</span>;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> /css {</span><br><span class="line"> <span class="attribute">sub_filter</span> <span class="string">'fonts.gstatic.com'</span> <span class="string">'fonts.yourdomain.com'</span>;</span><br><span class="line"> <span class="attribute">sub_filter_once</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">sub_filter_types</span> text/css;</span><br><span class="line"> <span class="attribute">proxy_pass_header</span> Server;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Host fonts.googleapis.com;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Accept-Encoding <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Real-IP $remote_addr;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Scheme $scheme;</span><br><span class="line"> <span class="attribute">proxy_pass</span> https://google;</span><br><span class="line"> <span class="attribute">proxy_cache</span> cache_one;</span><br><span class="line"> <span class="attribute">proxy_cache_valid</span> <span class="number">200</span> <span class="number">304</span> <span class="number">365d</span>;</span><br><span class="line"> <span class="attribute">proxy_cache_key</span> $host$uri$is_args$args;</span><br><span class="line"> <span class="attribute">expires</span> max;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> /icon {</span><br><span class="line"> <span class="attribute">sub_filter</span> <span class="string">'fonts.gstatic.com'</span> <span class="string">'fonts.yourdomain.com'</span>;</span><br><span class="line"> <span class="attribute">sub_filter_once</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">sub_filter_types</span> text/css;</span><br><span class="line"> <span class="attribute">proxy_pass_header</span> Server;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Host fonts.googleapis.com;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Accept-Encoding <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Real-IP $remote_addr;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Scheme $scheme;</span><br><span class="line"> <span class="attribute">proxy_pass</span> https://google;</span><br><span class="line"> <span class="attribute">proxy_cache</span> cache_one;</span><br><span class="line"> <span class="attribute">proxy_cache_valid</span> <span class="number">200</span> <span class="number">304</span> <span class="number">365d</span>;</span><br><span class="line"> <span class="attribute">proxy_cache_key</span> $host$uri$is_args$args;</span><br><span class="line"> <span class="attribute">expires</span> max;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> / {</span><br><span class="line"> <span class="attribute">proxy_pass_header</span> Server;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Host fonts.gstatic.com;</span><br><span class="line"> <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Real-IP $remote_addr;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> X-Scheme $scheme;</span><br><span class="line"> <span class="attribute">proxy_pass</span> https://gstatic;</span><br><span class="line"> <span class="attribute">proxy_cache</span> cache_one;</span><br><span class="line"> <span class="attribute">proxy_cache_valid</span> <span class="number">200</span> <span class="number">304</span> <span class="number">365d</span>;</span><br><span class="line"> <span class="attribute">proxy_cache_key</span> $host$uri$is_args$args;</span><br><span class="line"> <span class="attribute">expires</span> max;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Nginx</category>
</categories>
<tags>
<tag>Nginx</tag>
</tags>
</entry>
<entry>
<title>RESTful风格鉴权设计</title>
<url>/2017/09/30/spring-restful-auth/</url>
<content><![CDATA[<p>基于Spring及Redis的Token鉴权设计</p>
<h2 id="REST简介"><a href="#REST简介" class="headerlink" title="REST简介"></a>REST简介</h2><p>REST (Representational State Transfer) 是一种软件架构风格。它将服务端的信息和功能等所有事物统称为资源,客户端的请求实际就是对资源进行操作,它的主要特点有: 每一个资源都会对应一个独一无二的 url,客户端通过<br> HTTP 的 GET、POST、PUT、DELETE 请求方法对资源进行查询、创建、修改、删除操作。 客户端与服务端的交互必须是无状态的。</p>
<h2 id="Token身份鉴权"><a href="#Token身份鉴权" class="headerlink" title="Token身份鉴权"></a>Token身份鉴权</h2><p>网站应用一般使用 Session 进行登录用户信息的存储及验证,而在移动端使用 Token 则更加普遍。它们之间并没有太大区别,Token 比较像是一个更加精简的自定义的 Session。Session 的主要功能是保持会话信息,而 Token 则>只用于登录用户的身份鉴权。所以在移动端使用 Token 会比使用 Session 更加简易并且有更高的安全性,同时也更加符合 RESTful 中无状态的定义。</p>
<h2 id="交互流程"><a href="#交互流程" class="headerlink" title="交互流程"></a>交互流程</h2><ol>
<li>客户端通过登录请求提交用户名和密码,服务端验证通过后生成一个 Token 与该用户进行关联,并将 Token 返回给客户端。</li>
<li>客户端在接下来的请求中都会携带 Token,服务端通过解析 Token 检查登录状态。</li>
<li>当用户退出登录、其他终端登录同一账号(被顶号)、长时间未进行操作时 Token 会失效,这时用户需要重新登录。</li>
</ol>
<h2 id="程序示例"><a href="#程序示例" class="headerlink" title="程序示例"></a>程序示例</h2><p>服务端生成的 Token 一般为随机的非重复字符串,根据应用对安全性的不同要求,会将其添加时间戳(通过时间判断 Token 是否被盗用)或 url 签名(通过请求地址判断 Token 是否被盗用)后加密进行传输。在本文中为了演示方便,仅是将 User Id 与 Token 以”_”进行拼接。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Token 的 Model 类,可以增加字段提高安全性,例如时间戳、url 签名</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TokenModel</span> </span>{</span><br><span class="line"> <span class="comment">// 用户 id</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">long</span> userId;</span><br><span class="line"> <span class="comment">// 随机生成的 uuid</span></span><br><span class="line"> <span class="keyword">private</span> String token;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">TokenModel</span> <span class="params">(<span class="keyword">long</span> userId, String token)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.userId = userId;</span><br><span class="line"> <span class="keyword">this</span>.token = token;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">getUserId</span> <span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> userId;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUserId</span> <span class="params">(<span class="keyword">long</span> userId)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.userId = userId;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getToken</span> <span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> token;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setToken</span> <span class="params">(String token)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.token = token;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Redis 是一个 Key-Value 结构的内存数据库,用它维护 User Id 和 Token 的映射表会比传统数据库速度更快,这里使用 Spring-Data-Redis 封装的 TokenManager 对 Token 进行基础操作:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 对 token 进行操作的接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">TokenManager</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建一个 token 关联上指定用户</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> userId 指定用户的 id</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 生成的 token</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> TokenModel <span class="title">createToken</span> <span class="params">(<span class="keyword">long</span> userId)</span></span>;</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 检查 token 是否有效</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> model token</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 是否有效</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">checkToken</span> <span class="params">(TokenModel model)</span></span>;</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 从字符串中解析 token</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> authentication 加密后的字符串</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> TokenModel <span class="title">getToken</span> <span class="params">(String authentication)</span></span>;</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 清除 token</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> userId 登录用户的 id</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deleteToken</span> <span class="params">(<span class="keyword">long</span> userId)</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 通过 Redis 存储和验证 token 的实现类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisTokenManager</span> <span class="keyword">implements</span> <span class="title">TokenManager</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> RedisTemplate redis;</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setRedis</span> <span class="params">(RedisTemplate redis)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.redis = redis;</span><br><span class="line"> <span class="comment">// 泛型设置成 Long 后必须更改对应的序列化方案</span></span><br><span class="line"> redis.setKeySerializer (<span class="keyword">new</span> JdkSerializationRedisSerializer ());</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> TokenModel <span class="title">createToken</span> <span class="params">(<span class="keyword">long</span> userId)</span> </span>{</span><br><span class="line"> <span class="comment">// 使用 uuid 作为源 token</span></span><br><span class="line"> String token = UUID.randomUUID ().toString ().replace (<span class="string">"-"</span>, <span class="string">""</span>);</span><br><span class="line"> TokenModel model = <span class="keyword">new</span> TokenModel (userId, token);</span><br><span class="line"> <span class="comment">// 存储到 redis 并设置过期时间</span></span><br><span class="line"> redis.boundValueOps (userId).set (token, Constants.TOKEN_EXPIRES_HOUR, TimeUnit.HOURS);</span><br><span class="line"> <span class="keyword">return</span> model;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> TokenModel <span class="title">getToken</span> <span class="params">(String authentication)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (authentication == <span class="keyword">null</span> || authentication.length () == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> String [] param = authentication.split (<span class="string">"_"</span>);</span><br><span class="line"> <span class="keyword">if</span> (param.length != <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 使用 userId 和源 token 简单拼接成的 token,可以增加加密措施</span></span><br><span class="line"> <span class="keyword">long</span> userId = Long.parseLong (param [<span class="number">0</span>]);</span><br><span class="line"> String token = param [<span class="number">1</span>];</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TokenModel (userId, token);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">checkToken</span> <span class="params">(TokenModel model)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (model == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> String token = redis.boundValueOps (model.getUserId ()).get ();</span><br><span class="line"> <span class="keyword">if</span> (token == <span class="keyword">null</span> || !token.equals (model.getToken ())) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果验证成功,说明此用户进行了一次有效操作,延长 token 的过期时间</span></span><br><span class="line"> redis.boundValueOps (model.getUserId ()).expire (Constants.TOKEN_EXPIRES_HOUR, TimeUnit.HOURS);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deleteToken</span> <span class="params">(<span class="keyword">long</span> userId)</span> </span>{</span><br><span class="line"> redis.delete (userId);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>RESTful 中所有请求的本质都是对资源进行 CRUD 操作,所以登录和退出登录也可以抽象为对一个 Token 资源的创建和删除,根据该想法创建 Controller:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取和删除 token 的请求地址,在 Restful 设计中其实就对应着登录和退出登录的资源映射</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span> (<span class="string">"/tokens"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TokenController</span> </span>{</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> TokenManager tokenManager;</span><br><span class="line"> <span class="meta">@RequestMapping</span> (method = RequestMethod.POST)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResponseEntity <span class="title">login</span> <span class="params">(<span class="meta">@RequestParam</span> String username, <span class="meta">@RequestParam</span> String password)</span> </span>{</span><br><span class="line"> Assert.notNull (username, <span class="string">"username can not be empty"</span>);</span><br><span class="line"> Assert.notNull (password, <span class="string">"password can not be empty"</span>);</span><br><span class="line"> User user = userRepository.findByUsername (username);</span><br><span class="line"> <span class="keyword">if</span> (user == <span class="keyword">null</span> || <span class="comment">// 未注册</span></span><br><span class="line"> !user.getPassword ().equals (password)) { <span class="comment">// 密码错误</span></span><br><span class="line"> <span class="comment">// 提示用户名或密码错误</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ResponseEntity (ResultModel.error (ResultStatus.USERNAME_OR_PASSWORD_ERROR), HttpStatus.NOT_FOUND);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 生成一个 token,保存用户登录状态</span></span><br><span class="line"> TokenModel model = tokenManager.createToken (user.getId ());</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ResponseEntity (ResultModel.ok (model), HttpStatus.OK);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@RequestMapping</span> (method = RequestMethod.DELETE)</span><br><span class="line"> <span class="meta">@Authorization</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResponseEntity <span class="title">logout</span> <span class="params">(<span class="meta">@CurrentUser</span> User user)</span> </span>{</span><br><span class="line"> tokenManager.deleteToken (user.getId ());</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ResponseEntity (ResultModel.ok (), HttpStatus.OK);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个 Controller 中有两个自定义的注解分别是@Authorization和@CurrentUser,其中@Authorization用于表示该操作需要登录后才能进行:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在 Controller 的方法上使用此注解,该方法在映射时会检查用户是否登录,未登录返回 401 错误</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Target</span> (ElementType.METHOD)</span><br><span class="line"><span class="meta">@Retention</span> (RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Authorization {</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里使用 Spring 的拦截器完成这个功能,该拦截器会检查每一个请求映射的方法是否有@Authorization注解,并使用 TokenManager 验证 Token,如果验证失败直接返回 401 状态码(未授权):</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 自定义拦截器,判断此次请求是否有权限</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthorizationInterceptor</span> <span class="keyword">extends</span> <span class="title">HandlerInterceptorAdapter</span> </span>{</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> TokenManager manager;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">preHandle</span> <span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 如果不是映射到方法直接通过</span></span><br><span class="line"> <span class="keyword">if</span> (!(handler <span class="keyword">instanceof</span> HandlerMethod)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> HandlerMethod handlerMethod = (HandlerMethod) handler;</span><br><span class="line"> Method method = handlerMethod.getMethod ();</span><br><span class="line"> <span class="comment">// 从 header 中得到 token</span></span><br><span class="line"> String authorization = request.getHeader (Constants.AUTHORIZATION);</span><br><span class="line"> <span class="comment">// 验证 token</span></span><br><span class="line"> TokenModel model = manager.getToken (authorization);</span><br><span class="line"> <span class="keyword">if</span> (manager.checkToken (model)) {</span><br><span class="line"> <span class="comment">// 如果 token 验证成功,将 token 对应的用户 id 存在 request 中,便于之后注入</span></span><br><span class="line"> request.setAttribute (Constants.CURRENT_USER_ID, model.getUserId ());</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果验证 token 失败,并且方法注明了 Authorization,返回 401 错误</span></span><br><span class="line"> <span class="keyword">if</span> (method.getAnnotation (Authorization.class) != <span class="keyword">null</span>) {</span><br><span class="line"> response.setStatus (HttpServletResponse.SC_UNAUTHORIZED);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>@CurrentUser</code>注解定义在方法的参数中,表示该参数是登录用户对象。这里同样使用了 Spring 的解析器完成参数注入:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在 Controller 的方法参数中使用此注解,该方法在映射时会注入当前登录的 User 对象</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Target</span> (ElementType.PARAMETER)</span><br><span class="line"><span class="meta">@Retention</span> (RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> CurrentUser {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 增加方法注入,将含有 CurrentUser 注解的方法参数注入当前登录用户</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CurrentUserMethodArgumentResolver</span> <span class="keyword">implements</span> <span class="title">HandlerMethodArgumentResolver</span> </span>{</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">supportsParameter</span> <span class="params">(MethodParameter parameter)</span> </span>{</span><br><span class="line"> <span class="comment">// 如果参数类型是 User 并且有 CurrentUser 注解则支持</span></span><br><span class="line"> <span class="keyword">if</span> (parameter.getParameterType ().isAssignableFrom (User.class) &&</span><br><span class="line"> parameter.hasParameterAnnotation (CurrentUser.class)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">resolveArgument</span> <span class="params">(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 取出鉴权时存入的登录用户 Id</span></span><br><span class="line"> Long currentUserId = (Long) webRequest.getAttribute (Constants.CURRENT_USER_ID, RequestAttributes.SCOPE_REQUEST);</span><br><span class="line"> <span class="keyword">if</span> (currentUserId != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 从数据库中查询并返回</span></span><br><span class="line"> <span class="keyword">return</span> userRepository.findOne (currentUserId);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> MissingServletRequestPartException (Constants.CURRENT_USER_ID);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="一些细节"><a href="#一些细节" class="headerlink" title="一些细节"></a>一些细节</h2><p>登录请求一定要使用 HTTPS,否则无论 Token 做的安全性多好密码泄露了也是白搭<br>Token 的生成方式有很多种,例如比较热门的有 JWT(JSON Web Tokens)、OAuth 等。</p>
]]></content>
<categories>
<category>Spring</category>
</categories>
<tags>
<tag>Spring</tag>
<tag>Redis</tag>
</tags>
</entry>
<entry>
<title>《三体》书评</title>
<url>/2016/07/06/%E4%B8%89%E4%BD%93%E4%B9%A6%E8%AF%84/</url>
<content><![CDATA[<p>转载一篇《三体》书评</p>
<p>以下是我生活中的朋友,花了6个多小时(当然是熬夜)为我而赶制的《三体》评介,让我很感动。不管我内心上多么抵触类型小说,单凭他的这份真诚,我也要(在今年内)将《三体》读完。不赘言,将其精彩分享: 我读书一直是一目十行,速战速决,看完后基本上记不住主角的名字,故事情节也模模糊糊。特别是《三体》这样,从地球写到三体星系再写到宇宙的尽头、从文革写到地球毁灭再写到时间的终结,从三维空间写到十维空间再写到高维粒子的低维展开,从化学飞船写到曲率引擎再写到光墓安全声明,混沌迷乱,支离破碎。但它带给我的冲击却是实实在在的,就如同高维物体在低维空间的投影一样真实,一本书能做到这一点,足够了!</p>
<p>下面几个关键词是我一目十行之后印象最深的地方,姑且可作为书评。</p>
<h2 id="【科技】"><a href="#【科技】" class="headerlink" title="【科技】"></a>【科技】</h2><p>作为一个硬科幻的长篇小说,对科技的描述是核心所在,对于一个科幻迷或伪科幻迷来说(例如我),硬科幻总是能让人读起来酣畅淋漓。《三体》中对人类、三体人和诸神(能够改变物理规则的超级智慧生物)的科技都有过细致的描写或者想象,特别是对人类的科技发展(或者说是绝望中的科学探索)的过程着墨甚多,重点是光速和维度。我觉得《三体》写科技有两个特点:一是写了一个科学发展的过程。非科幻迷根本不用为文中各种没有听说过的专业术语发愁,你不用理解它到底是什么原理,只管随着故事往前走,你就能明白它的作用。所以这个很硬很硬的硬科幻小说,其实读起来一点都不费劲,科技描写是情节设计的需要,当然也是满足读者好奇心和探索欲的需要。(后来我找了不少书,恶补了不少关于引力波、中子波、曲率、维度、宇宙噪声、微波背景辐射、反物质等等知识,最后当然是没看懂)。二是将科技的发展推到了极致。什么是极致,就是想象力之外。呵呵,你能想象维度攻击吗?你能想象改变物理规则吗?你能想象多维空间吗?你能想象控制光速吗?你能想象游离在时间之外吗?你能想象纯能体吗?最后,你能想象宇宙中的生物能够重新规划设计宇宙吗(归零行动)?</p>
<p>什么是科技?是汽车、计算机和宇宙飞船?不是,科技是人类仰望星空时的梦想,科技是人类掌握自身命运的钥匙,科技是宇宙中最伟大的美,科技是人类对自身的终极关怀。从这个意义上讲,《三体》对科技的描述和设定,超越了作为一个科幻小说的所需要的范畴,甚至直指人类探索宇宙和生命终极意义的不懈努力。 </p>
<h2 id="【爱情】"><a href="#【爱情】" class="headerlink" title="【爱情】"></a>【爱情】</h2><p>《三体》对爱情的描写不多,在一个如此庞大叙事的小说里,是一个容易被忽略的地方,但云天明和程心的爱情(我觉得更应该是云天明一个人的爱情),却被我扎扎实实的记住了。云天明从小孤僻受冷落(貌似还有反人类倾向),只是因为程心让他觉得温暖和平等,就义无反顾的爱上了她(程心不爱他也不知道他爱她)。我不想讲这种爱有没有意义,我只想说后来云天明为程心的付出:他为她买了一颗星星(浪漫),他为她将自己的大脑送给了三体舰队(无畏),他为她冒死讲了那三个童话(执着),他为她留下了一个小宇宙(付出)。云天明靠自己一个人的力量,在这个宇宙黑暗森林严酷的法则下,给程心、给人类留下了最后也是唯一的一个生存的希望,尽管人类没有能够把握住。但是他依然默默的坚持、执着的付出,生命对他来说早已没有了意义,只有爱才是他活下来的唯一力量。</p>
<p>爱情是什么?可能也必须有千万种的答案,我不知道。云天明的爱情又是什么?是义无反顾?是默默付出?不是,他的爱是:因为爱一个人而爱整个人类。我始终不认为云天明拯救人类的行动是出于对人类的热爱和慈悲,他对人类并没有什么感情,但是因为程心他能做的都做到了。我其实也不理解大刘(作者)这么写想表达什么?是单纯的让我们感动?还是探讨个体与族群的归属与背离?还是一如既往的想把爱情这个千古命题推到极致,放在人类生死存亡的特殊时刻来进行考量?你们自己看吧。</p>
<h2 id="【绝境】"><a href="#【绝境】" class="headerlink" title="【绝境】"></a>【绝境】</h2><p>《三体》里人类不得不一次又一次的面对绝境,三体人一样,诸神(解释同上)也一样,谁都逃不开黑暗森林法则的束缚,甚至宇宙本身,也从十维降到了三维,不可避免的向绝境一步一步滑去。尽管有一次又一次的绝处逢生,《三体》最终还是指向了一个悲剧的结尾,就像不管多美丽的焰火,最终都会归于沉寂。你别说什么毕竟绽放过啊,历史会记住我们的,呵呵,大刘(作者)不给你这个机会,宇宙归零(没有时间之外的小宇宙了),什么都不会存在,包括时间和历史,新生的宇宙对过去一无所知。</p>
<p>我始终认为“面壁者计划”是小说中最美妙的构思(自己看吧,剧透就没意思了),也是最打动人心的绝境逢生的故事;而三体舰队飞向地球,然后迫不得已调转方向离开地球,驶向茫茫太空深处,是最悲壮最无奈的绝处求生的故事(特别喜欢这一点,大刘没有让三体人成为类似星际争霸虫族那样的掠夺者,他们有自己的痛苦和挣扎,有自己的梦想和奋斗,所以这不是一部星际战争小说,请不要这么侮辱它)。</p>
<p>是不是只有写孤注一掷的徒劳挣扎才能有如此入人心魄的力量?还是在黑暗森林法则的设定下,这,本身就是宿命!说到这里,我忽然想起当年看今何在的《悟空传》的时候,封面上有这么几句话:“我要这天,再遮不住我眼;我要这地,再埋不了我心;我要众生,皆明白我意;我要诸佛,都烟消云散!”这么多年了,我一直认为这是悟空向命运的挑战,现在看来,这何尝不是他绝望的怒吼!</p>
<h2 id="【图景】"><a href="#【图景】" class="headerlink" title="【图景】"></a>【图景】</h2><p>基于小说的主旨,前面的内容的确有点悲哀,是不是觉得心情沉重?那现在来点轻松的。王小波说:生活一定要有趣,同理可证,一本有趣的书才会有读者。《三体》是一本有趣的书,当然我不是指它的内容好笑,而是说,它能给你一些不一样的体验。《三体》讲述的是星球、星系和星云,讲述的是二维、四维和十维,讲述的是宇宙边缘、时间尽头和生命终点,总之一句话,讲述的是你做梦都不会梦到的东西。如何去想象?大刘给你放电影,带我们窥视他的宇宙。他用最细致的笔墨为你描绘出他脑海中的想象图景,例如:从四维空间看三维、空间坟墓、三体星系,智子的蚀刻,最经典的当然是二维化中的太阳系。那是一种在清冷星光下的凄美,那是一种一望无际的悲壮,那是一种穿过星际尘埃看到宇宙尽头的寂寞,呵呵,想象有多美,你就能看到多美。</p>
<p>《三体》中像这样细致的景物描写,比比皆是。就像当初看《独立日》,一个镜头记得特别清楚,就是外星飞船在火烧云的挟裹中,到达了全球各大城市上空,电影里给出了一组连续不断的闪现,华盛顿–纽约–东京–莫斯科……,就像我读完《三体》一样,脑子里总有好多的画面在不停的闪现,三体星系–空间坟墓–智子蚀刻–水滴……,等等,我有点晕了。</p>
<h2 id="【尺度-】"><a href="#【尺度-】" class="headerlink" title="【尺度 】"></a>【尺度 】</h2><p>这几年我有两种东西看的最多,一个是网上的各种小段子,另一种就是科幻类的东西。段子吗,都是调侃现实的,网上高人多呢,从段子里不仅能看到诡辩、狡辩,甚至还有点思辨的味道;科幻吗,都是超越现实的(不喜欢看玄幻,那是逃避现实的),有时候遇到些困难和迷茫,把它放在光年的尺度上去想一想,一切就豁然开朗。(顺便说一句什么是现实,现实就是这么晚了我他妈的还在写什么狗屁书评!)</p>
<p>智子来到地球以后,人类的微观物理研究就停滞了,因此《三体》里的叙事大部分是宏观尺度,是站在整个时空的高度来俯视众生,最小的单位都是恒星级的。例如叶文洁是通过太阳才发出信号,二向箔毁灭的是整个太阳系,诸神的战争引爆恒星是最低级的攻击方式,改变宇宙常数所需的能量是毁灭一个或者几个星系得来的等等。几万年也叫沧海桑田?那不过是一瞬;几光年也叫星际旅行?那不过是串门。在读《三体》的时候,总会不自觉的用诸神的视角来看世界,那种掌控宇宙纤毫、独立时间之外的俯视感,你以前没有体会过吧?什么时间旅行啊,什么银河系联盟啊,什么变形金刚啊,什么大怪兽哥斯拉啊,统统弱爆了,因为你就是宇宙与时间本身。</p>
<h2 id="【人性-】"><a href="#【人性-】" class="headerlink" title="【人性 】"></a>【人性 】</h2><p>最后不能不来讲讲人性,《三体》里很着力刻画表现人物冲突,例如叶文洁和地球拯救组织,面壁者与破壁者,历任执剑人,蓝色空间号,戴维,还有艾AA等,每个人物的个性鲜明、矛盾突出、冲突剧烈,很多是在人类生死存亡的极端条件下对人性的拷问和道德标准的再思考。看的出大刘在这方面是很认真的,但网上还是有人说大刘写人性差把火候,我觉得,不是写的不好,而是在如此庞大的叙事结构下,对比起来显得单薄了一些,除非再多些500万字。</p>
<p>还是要说一下程心(网上好像叫她圣母,很多人不喜欢她),她作为执剑人时,因为爱心泛滥(优柔寡断?)一手导致了三体人摧毁了地球的引力波发射装置,威慑失效,差点使人类落入万劫不复的深渊(虽然打击终究到来,地球被毁灭)。按下按钮,是毁灭地球和三体两个文明,只是毁灭的时间不确定,但打击终会到来;不按按钮,是三体人奴役地球,人类作为小白鼠继续生存。她选择了后者!</p>
<p>如果从宇宙的角度上看,三体和地球都是一样的智慧生物,种族的生生灭灭对久远的时间来说,都是过眼云烟,就如同恒星的毁灭与重生,选择谁不重要,与其都要灭亡,那为人类留下一粒种子到底对不对?如果从人类的角度来看, 与其苟延残喘的活着,不如轰轰烈烈的死去,人之所以为人,是因为不仅仅只是活着,还有尊严!(大刘这点有些太虐心的,总是搞这么难的选择题怎么做啊?)</p>
<p>我又想起了一个故事,小时看过的郑渊洁的童话《保卫叛逆者》(让我学学云天明,也来讲个童话),故事讲的是M星(比地球科技先进N倍)在地球上投放了四个生命的种子,时间到了就接走进行研究,可是这四个生命(一个男孩、一个女孩、一个老人和一只羊)与地球产生了千丝万缕不可割舍的感情,一致决定做一名地球人。可是M星不同意,坚决要接走这四名叛逆者,同时宣布:如果人类敢阻拦,就毁灭地球。人类进行了全球公投,一致同意保卫叛逆者。结果呢?我把最后一段(部分)抄下来:</p>
<p>地球是太空中最美丽的星球。</p>
<p>人类是宇宙中最美妙的生命。</p>
<p>当人类决定保卫叛逆者时, 以上两条就成了终极真理。</p>
<p>尊严、人格、精神、爱。</p>
<p>使人类充实的东西。</p>
<p>11点整,人们走出房屋,面对天空,等待M星球,地球静极了。在这个时刻,在这个人类为了保卫M星球四个叛逆者而可能献出50亿条生命的时刻,人类获得了永生。</p>
<p>12点整,数百个笼罩在浅蓝色光环中的飞碟列队出现在天空中,它们缓缓逼近地球。</p>
<p>最后,以上一切全凭记忆,有与《三体》冲突的地方,那是我没记清楚,呵呵。</p>
]]></content>
<categories>
<category>书评影评</category>
</categories>
<tags>
<tag>书评影评</tag>
</tags>
</entry>
<entry>
<title>AngularJS Form</title>
<url>/2016/07/06/AngularJS-Form/</url>
<content><![CDATA[<p>AngularJS provides a small and well-defined set of constructs that make standard form-based operations easier. For a form, we should consider three points:</p>
<ol>
<li>Allowing user input</li>
<li>Validating those inputs against business rules</li>
<li>Submitting the data to the backend server</li>
</ol>
<p><code>form</code> in AngularJS is extend from html <code>form</code>. Now it’s a directive. When Angular encounters the <code>form</code> tag, it executes the <code>form</code> directive. This directive creates an instance of a special Angular class <code>FormController</code> that is made available to us on the current scope. It’s this controller which provides an API to check and manipulate the state of the form. The same as <code>ng-model</code> which creates an instance of <code>NgModelController</code>.</p>
<h2 id="Validation"><a href="#Validation" class="headerlink" title="Validation"></a>Validation</h2><p>It already has <strong>built-in support for validating</strong>. Validations are automatically setup by AngularJS according to:</p>
<ul>
<li>Input type - text, numbers, e-mails, URLs, radios, checkboxes, and a few others. (such as <code><intput type='email'/></code>).</li>
<li>Validation attributes - <code>required</code>, <code>min</code>, <code>max</code>, and custom attributes such as <code>ng-pattern</code>, <code>ng-minlength</code>, and <code>ng-maxlength</code>.</li>
</ul>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- novalidate is used to disable browser's native form validation --></span></span><br><span class="line"><span class="tag"><<span class="name">form</span> <span class="attr">name</span>=<span class="string">"form"</span> <span class="attr">novalidate</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">for</span>=<span class="string">"form-name"</span>></span>Name:<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">name</span>=<span class="string">"formName"</span> <span class="attr">id</span>=<span class="string">"form-name"</span> <span class="attr">class</span>=<span class="string">"form-control"</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">ng-model</span>=<span class="string">"user.name"</span> <span class="attr">required</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.formName.$error.required && form.formName.$dirty"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Name is required<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"><span class="tag"></<span class="name">form</span>></span></span><br></pre></td></tr></table></figure>
<p>So AngularJS will check <code>required</code> attribute. When input is empty, the error label will be shown.</p>
<p>More details, <code>from</code> directive creates an instace of <code>FormController</code> and published into the scope using the name attribute. Then the inside <code>ng-model</code> create <code>NgModelController</code> and published as a property of the form instance using the name attribute. <code>$error</code> and $dirty is of <code>NgModelController</code>.</p>
<h3 id="error"><a href="#error" class="headerlink" title="$error"></a>$error</h3><p>It contains a list of all errors for the specific ng-model directive. From above example, <code>required</code> is validation attribute. So to check the validation, we need to follow this format:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">formName.inputName.$error.validation</span><br></pre></td></tr></table></figure>
<p>For example:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">formName.inputName.$error.required <span class="comment">// For required</span></span><br><span class="line">formName.inputName.$error.number <span class="comment">// For type="number"</span></span><br><span class="line">formName.inputName.$error.pattern <span class="comment">// For ng-pattern</span></span><br></pre></td></tr></table></figure>
<p><strong>When validation is failing, <code>$error.validation</code> will be true</strong>. We could use it to decide whether to show error messages.</p>
<h3 id="States-by-ng-model"><a href="#States-by-ng-model" class="headerlink" title="States by ng-model"></a>States by ng-model</h3><p>Besides <code>$error</code>, every element that uses ng-model — including input, textarea, and select — has states defined on the associated model controller:</p>
<ul>
<li>$pristine: True if user does not interact with the input. Any updates to the input field and $pristine is set to false. <strong>Once false, it never flips, unless we call the <code>$setPristine()</code> function on the model controller</strong>.</li>
<li>$dirty: Reverse of $pristine. This is true when the input data has been updated. <strong>This gets reset to false if <code>$setPristine()</code> is called.</strong></li>
<li>$touched: True if the control ever had focus.</li>
<li>$untouched: True if the control has never lost focus.</li>
<li>$valid: True if there are validations defined on the input element and none of them are failing.</li>
<li>$invalid: True if any of the validations defined on the element are failing.</li>
</ul>
<p>So from above example:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.formName.$error.required && form.formName.$dirty"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Name is required<span class="tag"></<span class="name">label</span>></span></span><br></pre></td></tr></table></figure>
<p>If there is no <code>form.formName.$dirty</code>, the validation message is shown as soon as we load the form. When user never interacts with the input, <code>$pristine</code> is true and <code>$pristine</code> is false. So here, the error message will never be shown until user begins to interact with the input.</p>
<h3 id="CSS-to-an-input-element"><a href="#CSS-to-an-input-element" class="headerlink" title="CSS to an input element"></a>CSS to an input element</h3><p>Based on the model state, Angular also adds some CSS classes automatically to an input element.</p>
<ul>
<li><code>ng-valid/ng-invalid</code>: This is used if the model is valid or not</li>
<li><code>ng-pristine/ng-dirty</code>: This is used if the model is pristine or ng-dirty.</li>
<li><code>ng-untouched/ng-touched</code>: This is used when the input is never visited or not.</li>
<li><code>ng-invalid-<errorkey>/ng-valid-<errorkey></code>: This is used for a specific failed/sucessed validation.</li>
<li><code>ng-empty/ng-not-empty</code>: This is used if the model is empty or not</li>
</ul>
<p>For example, when we check the page by Inspect of browser (not your own code), we could find a list of class already added in input:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"test"</span> <span class="attr">class</span>=<span class="string">"ng-pristine ng-untouched ng-invalid ng-invalid-required"</span> <span class="attr">...</span>></span></span><br></pre></td></tr></table></figure>
<p>When we type something, it could change to:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"test"</span> <span class="attr">class</span>=<span class="string">"ng-dirty ng-touched ng-valid ng-valid-required"</span> <span class="attr">...</span>></span></span><br></pre></td></tr></table></figure>
<p>So we could customize input element by these classes according to different state. For example:</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">input</span><span class="selector-class">.ng-invalid</span> {</span><br><span class="line"> <span class="attribute">border</span>:<span class="number">1px</span> solid blue;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="Points-to-be-careful"><a href="#Points-to-be-careful" class="headerlink" title="Points to be careful"></a>Points to be careful</h3><p><strong>If data in the model is invalid, it does not show up in the view and the view element is empty.</strong></p>
<p>For example, we set age value as “2” in controller in init method. But on the view, we set a “min=’5’” validation. So the input is empty when we load page.</p>
<h3 id="Error-messages-by-ng-messages"><a href="#Error-messages-by-ng-messages" class="headerlink" title="Error messages by ng-messages"></a>Error messages by ng-messages</h3><p><code>ng-messages</code> and <code>ng-message</code> that allow us to show/hide error messages with a less verbose syntax. It’s better to use them to show validation errors instead of <code>ng-show/ng-hide</code>.</p>
<blockquote>
<p>To use them, we need to add angular-messages.js and add ngMessages module.</p>
</blockquote>
<p>Think about a lot of validation in one input:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">form</span> <span class="attr">name</span>=<span class="string">"form"</span> <span class="attr">novalidate</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">for</span>=<span class="string">"test"</span>></span>Demo:<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"number"</span> <span class="attr">name</span>=<span class="string">"test"</span> <span class="attr">id</span>=<span class="string">"test"</span> <span class="attr">ng-model</span>=<span class="string">"user.demo"</span> <span class="attr">min</span>=<span class="string">"1"</span> <span class="attr">ng-pattern</span>=<span class="string">"/^-?\d+$/"</span> <span class="attr">required</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.test.$dirty && form.test.$error.required"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Required<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.test.$dirty && form.test.$error.number"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Must be number<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.test.$dirty && form.test.$error.min"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Min 1<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.test.$dirty && form.test.$error.pattern"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Must in right format<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"><span class="tag"></<span class="name">form</span>></span></span><br></pre></td></tr></table></figure>
<p>We could change it to:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">form</span> <span class="attr">name</span>=<span class="string">"form"</span> <span class="attr">novalidate</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">for</span>=<span class="string">"test"</span>></span>Demo:<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"number"</span> <span class="attr">name</span>=<span class="string">"test"</span> <span class="attr">id</span>=<span class="string">"test"</span> <span class="attr">ng-model</span>=<span class="string">"user.demo"</span> <span class="attr">min</span>=<span class="string">"1"</span> <span class="attr">ng-pattern</span>=<span class="string">"/^-?\d+$/"</span> <span class="attr">required</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">ng-messages</span>=<span class="string">"form.test.$error"</span> <span class="attr">ng-if</span>=<span class="string">"form.test.$dirty"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-message</span>=<span class="string">"required"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Required<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-message</span>=<span class="string">"number"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Must be number<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-message</span>=<span class="string">"min"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Min 1<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">ng-message</span>=<span class="string">"pattern"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Must in right format<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">form</span>></span></span><br></pre></td></tr></table></figure>
<h3 id="ng-messages-multiple"><a href="#ng-messages-multiple" class="headerlink" title="ng-messages-multiple"></a>ng-messages-multiple</h3><p>With above example, each time, only one message will be shown. (Always the upper one has higher priority). When user typed “-1”, both <code>$error.min</code> and <code>$error.pattern</code> will be true. But only error message for <code>min</code> will be shown. To avoid this problem:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-messages</span>=<span class="string">"..."</span> <span class="attr">ng-messages-multiple</span>></span></span><br></pre></td></tr></table></figure>
<h3 id="Message-reuse-and-override"><a href="#Message-reuse-and-override" class="headerlink" title="Message reuse and override"></a>Message reuse and override</h3><h4 id="Reuse"><a href="#Reuse" class="headerlink" title="Reuse"></a>Reuse</h4><p>Because many messages are the same in a big and complex form, we could reuse them by defining all messages in a sepereted file:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-message</span>=<span class="string">"required"</span>></span>This field is required<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-message</span>=<span class="string">"minlength"</span>></span>This field is too short<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-message</span>=<span class="string">"maxlength"</span>></span>This field is too long<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-message</span>=<span class="string">"required"</span>></span>This field is required<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-message</span>=<span class="string">"email"</span>></span>This needs to be a valid email<span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<p>And include it by <code>ng-messages-include</code>:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-messages</span>=<span class="string">"form.test.$error"</span> <span class="attr">ng-if</span>=<span class="string">"form.test.$dirty"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">ng-messages-include</span>=<span class="string">"fileName.html"</span>></span><span class="tag"></<span class="name">div</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<h4 id="Override"><a href="#Override" class="headerlink" title="Override"></a>Override</h4><p>If generic messages are not enough to match all input fields, we could override messages defined in the remote template by redefining them within the directive container.</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-messages</span>=<span class="string">"form.test.$error"</span> <span class="attr">ng-if</span>=<span class="string">"form.test.$dirty"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">ng-message</span>=<span class="string">"required"</span>></span>Override the required message<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="comment"><!-- Must put override ones above template --></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">ng-messages-include</span>=<span class="string">"fileName.html"</span>></span><span class="tag"></<span class="name">div</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<h3 id="Custom-validation"><a href="#Custom-validation" class="headerlink" title="Custom validation"></a>Custom validation</h3><p>There two ways to create a custom validation:</p>
<ol>
<li>By <a href="https://htmlpreview.github.io/?https://github.com/angular-ui/ui-validate/master/demo/index.html">AngularJS-UI</a></li>
<li>By our own directive</li>
</ol>
<h4 id="By-AngularJS-UI"><a href="#By-AngularJS-UI" class="headerlink" title="By AngularJS-UI"></a>By AngularJS-UI</h4><p>Check <a href="https://htmlpreview.github.io/?https://github.com/angular-ui/ui-validate/master/demo/index.html">AngularJS-UI</a></p>
<h4 id="By-our-onw-directive"><a href="#By-our-onw-directive" class="headerlink" title="By our onw directive"></a>By our onw directive</h4><p>Define a directive:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// A validation works like a blacklist, user input can not be in this list</span></span><br><span class="line">app.directive(<span class="string">'blacklist'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ </span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="built_in">require</span>: <span class="string">'ngModel'</span>,</span><br><span class="line"> link: <span class="function"><span class="keyword">function</span>(<span class="params">scope, elem, attr, ngModel</span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> blacklist = attr.blacklist.split(<span class="string">','</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//For DOM -> model validation</span></span><br><span class="line"> ngModel.$parsers.unshift(<span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> valid = blacklist.indexOf(value) === -<span class="number">1</span>;</span><br><span class="line"> ngModel.$setValidity(<span class="string">'blacklist'</span>, valid);</span><br><span class="line"> <span class="keyword">return</span> valid ? value : <span class="literal">undefined</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">//For model -> DOM validation</span></span><br><span class="line"> ngModel.$formatters.unshift(<span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>{</span><br><span class="line"> ngModel.$setValidity(<span class="string">'blacklist'</span>, blacklist.indexOf(value) === -<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">return</span> value;</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>
<p>Add it in input element:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">form</span> <span class="attr">name</span>=<span class="string">"form"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"type"</span> <span class="attr">ng-model</span>=<span class="string">"data.fruitName"</span> <span class="attr">blacklist</span>=<span class="string">"coconuts,bananas,pears"</span> <span class="attr">required</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">ng-show</span>=<span class="string">"myForm.fruitName.$error.blacklist"</span>></span> The phrase "{{data.fruitName}}" is blacklisted<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">ng-show</span>=<span class="string">"myForm.fruitName.$error.required"</span>></span>required<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"><span class="tag"></<span class="name">form</span>></span></span><br></pre></td></tr></table></figure>
<h2 id="Submit"><a href="#Submit" class="headerlink" title="Submit"></a>Submit</h2><p>Form in Angular has a different role to play as compared to traditional html form that posts data to the server. We could not find <code>action</code> attribute. So how to submit data?!</p>
<p>The standard form behavior of posting data to the server using full-page post-back does not make sense with a SPA framework such as AngularJS. In Angular, <strong>all server requests are made through AJAX</strong> invocations originating from controllers, directives, or services. While the traditional one will refresh the whole page.</p>
<p>So two ways to do it:</p>
<ol>
<li>By <code>ng-submit</code></li>
</ol>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">form</span> <span class="attr">name</span>=<span class="string">"form"</span> <span class="attr">ng-submit</span>=<span class="string">"submit()"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">"submit"</span> <span class="attr">ng-disabled</span>=<span class="string">"form.$invalid"</span>></span>Submit<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">form</span>></span></span><br></pre></td></tr></table></figure>
<ol start="2">
<li>By binding function to the button directly</li>
</ol>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">form</span> <span class="attr">name</span>=<span class="string">"form"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">ng-click</span>=<span class="string">"submit()"</span> <span class="attr">ng-disabled</span>=<span class="string">"form.$invalid"</span>></span>Submit<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">form</span>></span></span><br></pre></td></tr></table></figure>
<p>Then, we need to know that from controller also has some APIs and properties as model controller:</p>
<ul>
<li><code>$setValidity(validationKey, status, childController)</code>: This is similar to the <code>$setValidity</code> API of <code>NgModelController</code> but is used to set the validation state of the model controller inside form controller.</li>
<li><code>$setDirty()</code>: This is used to mark the form dirty.</li>
<li><code>$setPristine()</code>: This is used to make the form pristine. This is often used to mark the form pristine after persisting the data to server. The $setPristine call propagates to all model controllers registered with the form, so <strong>all child inputs are also set back to the pristine state</strong>.</li>
<li><code>$setUntouched()</code>: This is used to mark the form untouched. This is mostly called in sync with <code>$setPristine</code>, after data is submitted.</li>
</ul>
<p>Other than the state manipulation API, there are some handy properties:</p>
<ul>
<li>$pristine</li>
<li>$dirty</li>
<li>$valid</li>
<li>$invalid</li>
<li>$error</li>
</ul>
<p>They are similar to model controller properties except for the $error property. It’s in fact a bit more complex. <strong>It aggregates all failures across all contained inputs</strong>. The <code>$error</code>‘s property corresponds to the failing error condition and the value is an array of controllers that are invalid. For example, If there are three <code>required</code> errors, <code>$error.required</code> should be an array with three controllers with this kind of error.</p>
<p>Any way, we <strong>use the $invalid property of the form controller to verify if there are validation errors before we perform a submit</strong>.</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">ng-class</span>=<span class="string">"{'btn-default':formName.$valid,'btn-warning':formName.$invalid}"</span> <span class="attr">ng-click</span>=<span class="string">"submit()"</span> <span class="attr">ng-disabled</span>=<span class="string">"formName.$invalid"</span>></span>Submit<span class="tag"></<span class="name">button</span>></span></span><br></pre></td></tr></table></figure>
<p>So submit button will only be available when no validation errors of its elements. We also use <code>$valid/$invalid</code> to set css :D.</p>
<h3 id="Points-to-be-careful-1"><a href="#Points-to-be-careful-1" class="headerlink" title="Points to be careful"></a>Points to be careful</h3><p>In general, instead of disabling a submit button, we prefer to inform user all validation errors when user clicks submit button.<br>If we remove <code>ng-disabled</code>, when user loads the page and clicks submit button directly without touching others. Nothing is submitted as the form is invalid, but validation errors on its child elements like input do not show up at all. Look at one label:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.formName.$error.required && form.formName.$dirty"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Name is required<span class="tag"></<span class="name">label</span>></span></span><br></pre></td></tr></table></figure>
<p><code>form.formName.$dirty</code> disables validation messages until user has touched the element. It’s why here.</p>
<p>So in <code>submit()</code>, we could set a flag to fix this problem.</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">$scope.submit = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"></span><br><span class="line"> $scope.submitted = <span class="literal">true</span>; <span class="comment">// Will force validations</span></span><br><span class="line"> <span class="keyword">if</span> ($scope.form.$invalid) <span class="keyword">return</span>; <span class="comment">// Nothing will be submitted if invalid!</span></span><br><span class="line"></span><br><span class="line"> $scope.form.$setPristine();</span><br><span class="line"> $scope.submitted = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Other operations</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>So now update label:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"form.formName.$error.required && (submitted || form.formName.$dirty)"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Name is required<span class="tag"></<span class="name">label</span>></span></span><br></pre></td></tr></table></figure>
<p>When user clicks submit button, we force validation to be true!. But still a problem here, we need to repeat this fix on all labels and a little complex to read. So why not create a function because all validations need to check <code>submitted</code></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">$scope.hasError = <span class="function"><span class="keyword">function</span> (<span class="params">modelController, error</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> (modelController.$dirty || $scope.submitted) && error;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>So now update label again:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">label</span> <span class="attr">ng-show</span>=<span class="string">"hasError(formName.inputName, formName.inputName.$error.required)"</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>></span>Name is required<span class="tag"></<span class="name">label</span>></span></span><br></pre></td></tr></table></figure>
<p>For the moment, we only need to pass model controller and its validation without caring the conditions!</p>
<h2 id="Reset"><a href="#Reset" class="headerlink" title="Reset"></a>Reset</h2><p>The standard way to reset a form is to call the reset method on the form object such as <code>document.forms["formName"].reset()</code> or to use <code>type="reset"</code> for a button.</p>
<p>But we could also define a function by ourselves to add on the reset button</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">$scope.reset = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> $scope.formName.$setPristine();</span><br><span class="line"> <span class="comment">// Restore other parameters</span></span><br><span class="line"> <span class="comment">// Other things to do</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h2 id="Ref"><a href="#Ref" class="headerlink" title="Ref"></a>Ref</h2><ul>
<li><a href="https://docs.angularjs.org/guide/forms">AngularJS Form</a></li>
<li><a href="https://www.packtpub.com/web-development/angularjs-example">AngularJS by Example</a></li>
<li><a href="https://docs.angularjs.org/api/ngMessages">AngularJS ngMessage</a></li>
<li><a href="http://stackoverflow.com/questions/12581439/how-to-add-custom-validation-to-an-angularjs-form">Stackover Custom validation</a></li>
</ul>
]]></content>
<categories>
<category>AngularJS</category>
</categories>
<tags>
<tag>AngularJS</tag>
</tags>
</entry>
<entry>
<title>基于 OpenResty + GitHub WebHook 的代码自动更新</title>
<url>/2020/12/02/nginx-lua-webhook/</url>
<content><![CDATA[<h2 id="OpenResty"><a href="#OpenResty" class="headerlink" title="OpenResty"></a>OpenResty</h2><p>OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。</p>
<p>安装及使用请参阅<a href="https://openresty.org/">OpenResty官网</a></p>
<h2 id="Nginx配置"><a href="#Nginx配置" class="headerlink" title="Nginx配置"></a>Nginx配置</h2><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">server</span> {</span><br><span class="line"> <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"> <span class="attribute">server_name</span> yourdomain;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">location</span> /webhook {</span><br><span class="line"> <span class="attribute">default_type</span> <span class="string">'text/plain'</span>;</span><br><span class="line"> <span class="attribute">content_by_lua_file</span> /your/lua/path/webhook.lua;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="webhook-lua-源码"><a href="#webhook-lua-源码" class="headerlink" title="webhook.lua 源码"></a>webhook.lua 源码</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="keyword">local</span> GITHUB_WEBHOOK_SECRET = <span class="string">"your github webhook secret"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> request_method = ngx.var.request_method</span><br><span class="line"><span class="keyword">if</span> <span class="string">"POST"</span> ~= request_method <span class="keyword">then</span></span><br><span class="line"> ngx.<span class="built_in">exit</span>(<span class="number">404</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> signature = ngx.req.get_headers()[<span class="string">"X-Hub-Signature"</span>]</span><br><span class="line"><span class="keyword">if</span> signature == <span class="literal">nil</span> <span class="keyword">then</span></span><br><span class="line"> <span class="keyword">return</span> ngx.<span class="built_in">exit</span>(<span class="number">404</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">ngx.req.read_body()</span><br><span class="line"><span class="keyword">local</span> req_body = ngx.req.get_body_data()</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> req_body <span class="keyword">then</span></span><br><span class="line"> <span class="keyword">return</span> ngx.<span class="built_in">exit</span>(<span class="number">404</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> dt = {}</span><br><span class="line"><span class="keyword">for</span> k, v <span class="keyword">in</span> <span class="built_in">string</span>.<span class="built_in">gmatch</span>(signature, <span class="string">"(%w+)=(%w+)"</span>) <span class="keyword">do</span></span><br><span class="line"> dt[k] = v</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> str = <span class="built_in">require</span> <span class="string">"resty.string"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> digest = ngx.hmac_sha1(GITHUB_WEBHOOK_SECRET, req_body)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> str.to_hex(digest) == dt[<span class="string">"sha1"</span>] <span class="keyword">then</span></span><br><span class="line"> ngx.<span class="built_in">log</span>(ngx.ERR, <span class="string">"signature error"</span>)</span><br><span class="line"> <span class="keyword">return</span> ngx.<span class="built_in">exit</span>(<span class="number">404</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">os</span>.<span class="built_in">execute</span>(<span class="string">"cd /your/blog/repositorie/path/ && git pull"</span>);</span><br><span class="line">ngx.say(<span class="string">"OK"</span>)</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Nginx</category>
</categories>
<tags>
<tag>Nginx</tag>
<tag>OpenResty</tag>
</tags>
</entry>
<entry>
<title>Windows Terminal 美化与包管理工具</title>
<url>/2021/11/24/windows-terminal/</url>
<content><![CDATA[<h2 id="Windows-Terminal-安装"><a href="#Windows-Terminal-安装" class="headerlink" title="Windows Terminal 安装"></a>Windows Terminal 安装</h2><p><code>请参阅</code> <a href="https://docs.microsoft.com/zh-cn/windows/terminal/get-started">安装和设置 Windows 终端</a></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> "actions": [</span><br><span class="line"> // 自定义快捷键</span><br><span class="line"> { "command": "closeWindow", "keys": "alt+F4" },</span><br><span class="line"> { "command": "nextTab", "keys": "alt+shift+]" },</span><br><span class="line"> { "command": "prevTab", "keys": "alt+shift+[" },</span><br><span class="line"> { "command": "nextTab", "keys": "ctrl+alt+right" },</span><br><span class="line"> { "command": "prevTab", "keys": "ctrl+alt+left" },</span><br><span class="line"> { "command": "closeTab", "keys": "alt+w" },</span><br><span class="line"> { "command": "closeTab", "keys": "ctrl+w" },</span><br><span class="line"> { "command": "newTab", "keys": "alt+t" },</span><br><span class="line"> { "command": "newTab", "keys": "ctrl+t" },</span><br><span class="line"> { "command": "newTab", "keys": "ctrl+shift+t" },</span><br><span class="line"> { "command": "duplicateTab", "keys": "ctrl+shift+d" },</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="Windows-Terminal-美化"><a href="#Windows-Terminal-美化" class="headerlink" title="Windows Terminal 美化"></a>Windows Terminal 美化</h2><p><code>请参阅</code> <a href="https://docs.microsoft.com/zh-cn/windows/terminal/tutorials/powerline-setup">教程:在 Windows 终端中设置 Powerline</a></p>
<h3 id="Nerd-Fonts"><a href="#Nerd-Fonts" class="headerlink" title="Nerd Fonts"></a>Nerd Fonts</h3><p>Oh My Posh was designed to use <a href="https://www.nerdfonts.com/">Nerd Fonts</a>. Nerd Fonts are popular fonts that are patched to include icons. We recommend <a href="https://github.com/ryanoasis/nerd-fonts/releases/download/v2.1.0/Meslo.zip">Meslo LGM NF</a>, but any Nerd Font should be compatible with the standard <a href="https://github.com/JanDeDobbeleer/oh-my-posh/tree/main/themes">themes</a>.</p>
<h3 id="安装-Powerline-字体"><a href="#安装-Powerline-字体" class="headerlink" title="安装 Powerline 字体"></a>安装 Powerline 字体</h3><p>可以使用scoop安装,也可以从 <a href="https://github.com/microsoft/cascadia-code/releases">Cascadia Code GitHub发布页</a> 安装这些字体。</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 搜索字体相关信息</span></span><br><span class="line">scoop search CascadiaCode</span><br><span class="line"><span class="meta">#</span><span class="bash"> 搜索结果如下</span></span><br><span class="line">'nerd-fonts' bucket:</span><br><span class="line"> CascadiaCode-NF-Mono (2.1.0)</span><br><span class="line"> CascadiaCode-NF (2.1.0)</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 添加字体库的bucket</span></span><br><span class="line">scoop bucket add nerd-fonts</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 安装字体,需要申请管理员权限,若无sudo请先安装 scoop install sudo</span></span><br><span class="line">sudo scoop install CascadiaCode-NF</span><br></pre></td></tr></table></figure>
<p><strong>修改字体配置</strong></p>
<p>Windows PowerShell 配置文件 settings.json 文件现在应如下所示:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line">// If enabled, selections are automatically copied to your clipboard.</span><br><span class="line">"copyOnSelect": true,</span><br><span class="line">"profiles":</span><br><span class="line"> {</span><br><span class="line"> "defaults":</span><br><span class="line"> {</span><br><span class="line"> // Put settings here that you want to apply to all profiles.</span><br><span class="line"> "fontFace": "Cascadia Code PL"</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="设置-Powerline"><a href="#设置-Powerline" class="headerlink" title="设置 Powerline"></a>设置 Powerline</h3><p>如果尚未安装,请 <a href="https://git-scm.com/downloads">安装适用于 Windows 的 Git</a> 。</p>
<p>使用 PowerShell,安装 Posh-Git 和 Oh-My-Posh:</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">Install-Module posh-git -Scope CurrentUser</span><br><span class="line">Install-Module oh-my-posh -Scope CurrentUser</span><br><span class="line"><span class="meta">#</span><span class="bash"> 安装 Get-ChildItemColor 为 PowerShell 的输出添加颜色(比如为 ls 的输出上色)</span></span><br><span class="line">Install-Module -AllowClobber Get-ChildItemColor -Scope CurrentUser</span><br></pre></td></tr></table></figure>
<p><strong>自定义 PowerShell 提示符</strong></p>
<p>使用 notepad $PROFILE 或所选的文本编辑器打开 PowerShell 配置文件。 这不是你的 Windows 终端配置文件。 你的 PowerShell 配置文件是一个脚本,该脚本在每次启动 PowerShell 时运行。 <a href="https://docs.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7">详细了解 PowerShell 配置文件</a> 。<br>在 PowerShell 配置文件中,将以下内容添加到文件的末尾:</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">Import-Module posh-git</span><br><span class="line">Import-Module oh-my-posh</span><br><span class="line">Import-Module Get-ChildItemColor</span><br><span class="line">Set-Alias ll Get-ChildItem # 设置ll别名</span><br><span class="line">Set-Theme Robbyrussell</span><br></pre></td></tr></table></figure>
<p>现在,每个新实例启动时都会导入 Posh-Git 和 Oh-My-Posh,然后从 Oh-My-Posh 设置 Paradox 主题。 Oh-My-Posh 附带了若干<a href="https://github.com/JanDeDobbeleer/oh-my-posh#themes">内置主题</a> 。</p>
<h2 id="Windows-包管理工具安装"><a href="#Windows-包管理工具安装" class="headerlink" title="Windows 包管理工具安装"></a>Windows 包管理工具安装</h2><h3 id="winget"><a href="#winget" class="headerlink" title="winget"></a>winget</h3><p><strong>可使用多种方法安装 winget 工具:</strong></p>
<ul>
<li><a href="https://www.microsoft.com/p/app-installer/9nblggh4nns1?ocid=9nblggh4nns1_ORSEARCH_Bing&rtc=1&activetab=pivot:overviewtab">Windows 应用安装程序</a> 的外部测试版或预览版中包含 winget 工具。 必须安装 应用安装程序 的预览版本才能使用 winget 。 若要获取提前访问权限,请将你的请求提交到 <a href="https://aka.ms/AppInstaller_InsiderProgram">Windows 程序包管理器预览体验计划</a> 。 参与外部测试版 Ring 将保证你可以看到最新的预览版更新。</li>
<li>参与 <a href="https://insider.windows.com/">Windows 外部测试版 Ring</a> 。</li>
<li>安装位于 <a href="https://github.com/microsoft/winget-cli">winget 存储库</a> 的 release 文件夹中的 Windows 桌面应用安装程序包。</li>
</ul>
<p><code>请参阅</code> <a href="https://docs.microsoft.com/zh-cn/windows/package-manager/winget/">使用 winget 工具安装和管理应用程序</a><br><em>winget 工具需要 Windows 10 版本 1709 (10.0.16299) 或更高版本的 Windows 10。</em></p>
<p>Usage Example:</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">winget -v</span><br><span class="line"><span class="meta">#</span><span class="bash"> or</span></span><br><span class="line">winget install postman --rainbow</span><br></pre></td></tr></table></figure>
<h3 id="scoop"><a href="#scoop" class="headerlink" title="scoop"></a>scoop</h3><p><code>请参阅</code> <a href="https://scoop.sh/">scoop.sh</a></p>
<p>Scoop installs the tools you know and love</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">scoop install curl</span><br></pre></td></tr></table></figure>
<p>Make sure PowerShell 5 (or later, include PowerShell Core) and .NET Framework 4.5 (or later) are installed. Then run:</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')</span><br><span class="line"><span class="meta">#</span><span class="bash"> or shorter</span></span><br><span class="line">iwr -useb get.scoop.sh | iex</span><br></pre></td></tr></table></figure>
<p>Note: if you get an error you might need to change the execution policy (i.e. enable Powershell) with</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">Set-ExecutionPolicy RemoteSigned -scope CurrentUser</span><br></pre></td></tr></table></figure>
<p><strong>推荐安装软件</strong></p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">scoop install git # scoop依赖git</span><br><span class="line">scoop install 7zip # scoop依赖7zip解压</span><br><span class="line">scoop install aria2 # 可以考虑开启aria2下载</span><br><span class="line">scoop install sudo # 申请管理员权限,和linux下sudo命令相似,全局安装必备</span><br></pre></td></tr></table></figure>
<p><strong>List</strong></p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">scoop list # 列出已安装软件</span><br><span class="line">scoop bucket list # 列出bucket()</span><br></pre></td></tr></table></figure>
<p><strong>Bucket</strong></p>
<p>每个bucket中维护了许多json文件,描述了软件的相关信息,安装方式,更新方式,可以理解为软件源(虽然并不相同),默认只有一个main,几乎全是命令行工具,可以添加其他的:</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">scoop bucket known # 列出已知的bucket</span><br><span class="line">scoop bucket list # 列出已添加的bucket</span><br><span class="line">scoop bucket add extras # 添加bucket</span><br><span class="line">scoop bucket rm games # 移除bucket</span><br></pre></td></tr></table></figure>
<p><strong>推荐的bucket</strong></p>
<ul>
<li>main</li>
<li>extras</li>
</ul>
<p><em>因不符合main收录标准,但常用的一些库</em></p>
<ul>
<li>versions</li>
</ul>
<p><em>安装特定版本</em></p>
<ul>
<li>nerd-fonts</li>
</ul>
<h3 id="chocolatey"><a href="#chocolatey" class="headerlink" title="chocolatey"></a>chocolatey</h3><p><code>请参阅</code> <a href="https://chocolatey.org/install#individual">Chocolatey.org</a></p>
<p>PowerShell 安装 Chocolatey 非常简单,管理员运行,然后输入如下命令</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))</span><br></pre></td></tr></table></figure>
<p>Usage Example:</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">choco</span><br><span class="line"><span class="meta">#</span><span class="bash"> or</span></span><br><span class="line">choco -?</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>System</tag>
<tag>开发工具</tag>
</tags>
</entry>
<entry>
<title>OAuth2 + JWT 授权认证服务</title>
<url>/2017/10/20/oauth2-jwt/</url>
<content><![CDATA[<p>OAuth2 + JWT 授权认证服务</p>
<h2 id="参考阅读"><a href="#参考阅读" class="headerlink" title="参考阅读"></a>参考阅读</h2><ul>
<li><a href="http://www.jianshu.com/p/ec9b7bc47de9">Spring Security整合JSON Web Token(JWT)提升REST安全性</a></li>
<li><a href="http://geek.csdn.net/news/detail/236321">Spring Cloud下微服务权限方案</a></li>
<li><a href="http://www.jianshu.com/p/6307c89fe3fa">使用JWT和Spring Security保护REST API</a></li>
<li><a href="http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html">理解OAuth 2.0</a></li>
</ul>
<h2 id="JWT-简单介绍"><a href="#JWT-简单介绍" class="headerlink" title="JWT 简单介绍"></a>JWT 简单介绍</h2><h3 id="JWT长什么样"><a href="#JWT长什么样" class="headerlink" title="JWT长什么样"></a>JWT长什么样</h3><p>JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDg0OTA1NjMsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI3YjNiNTBlMC1hMjhjLTQ4NTQtYWNhMi0zMmQwZTIyZTY1ZmUiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhbGwiXX0.hPhvl1XbBMjoPER7BggWD1UO83o8SNtgg15rlUBndDo</span><br></pre></td></tr></table></figure>
<h3 id="JWT的构成"><a href="#JWT的构成" class="headerlink" title="JWT的构成"></a>JWT的构成</h3><p>第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).</p>
<h4 id="header"><a href="#header" class="headerlink" title="header"></a>header</h4><h5 id="jwt的头部承载两部分信息"><a href="#jwt的头部承载两部分信息" class="headerlink" title="jwt的头部承载两部分信息"></a>jwt的头部承载两部分信息</h5><ul>
<li>声明类型,这里是jwt</li>
<li>声明加密的算法 通常直接使用 HMAC SHA256</li>
</ul>
<p>完整的头部就像下面这样的JSON:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"alg"</span>: <span class="string">"HS256"</span>,</span><br><span class="line"> <span class="attr">"typ"</span>: <span class="string">"JWT"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后将头部进行base64编码,构成了第一部分</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</span><br></pre></td></tr></table></figure>
<h4 id="playload"><a href="#playload" class="headerlink" title="playload"></a>playload</h4><p>载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分</p>
<ul>
<li>标准中注册的声明</li>
<li>公共的声明</li>
<li>私有的声明</li>
</ul>
<h5 id="标准中注册的声明-建议但不强制使用"><a href="#标准中注册的声明-建议但不强制使用" class="headerlink" title="标准中注册的声明 (建议但不强制使用)"></a>标准中注册的声明 (建议但不强制使用)</h5><ul>
<li>iss: jwt签发者</li>
<li>sub: jwt所面向的用户</li>
<li>aud: 接收jwt的一方</li>
<li>exp: jwt的过期时间,这个过期时间必须要大于签发时间</li>
<li>nbf: 定义在什么时间之前,该jwt都是不可用的.</li>
<li>iat: jwt的签发时间</li>
<li>jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。</li>
</ul>
<h5 id="公共的声明"><a href="#公共的声明" class="headerlink" title="公共的声明"></a>公共的声明</h5><p>公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.</p>
<h5 id="私有的声明"><a href="#私有的声明" class="headerlink" title="私有的声明"></a>私有的声明</h5><p>私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64编码是可逆的,意味着该部分信息可以归类为明文信息。</p>
<p>定义一个payload:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"exp"</span>: <span class="number">1508490563</span>,</span><br><span class="line"> <span class="attr">"user_name"</span>: <span class="string">"admin"</span>,</span><br><span class="line"> <span class="attr">"authorities"</span>: [</span><br><span class="line"> <span class="string">"ROLE_ADMIN"</span>,</span><br><span class="line"> <span class="string">"ROLE_USER"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"jti"</span>: <span class="string">"7b3b50e0-a28c-4854-aca2-32d0e22e65fe"</span>,</span><br><span class="line"> <span class="attr">"client_id"</span>: <span class="string">"abc"</span>,</span><br><span class="line"> <span class="attr">"scope"</span>: [</span><br><span class="line"> <span class="string">"all"</span></span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后将其进行base64编码,得到Jwt的第二部分</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">eyJleHAiOjE1MDg0OTA1NjMsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI3YjNiNTBlMC1hMjhjLTQ4NTQtYWNhMi0zMmQwZTIyZTY1ZmUiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhbGwiXX0</span><br></pre></td></tr></table></figure>
<h4 id="signature"><a href="#signature" class="headerlink" title="signature"></a>signature</h4><p>jwt的第三部分是一个签证信息,这个签证信息由三部分组成:</p>
<ul>
<li>header (base64后的)</li>
<li>payload (base64后的)</li>
<li>secret</li>
</ul>
<p>这个部分需要base64编码后的header和base64编码后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// javascript</span><br><span class="line">var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);</span><br><span class="line">var signature = HMACSHA256(encodedString, 'secret');</span><br></pre></td></tr></table></figure>
<p>将这三部分用.连接成一个完整的字符串,构成了最终的jwt</p>
<p><strong>注意</strong></p>
<p>secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。</p>
<h2 id="OAuth2-介绍及使用"><a href="#OAuth2-介绍及使用" class="headerlink" title="OAuth2 介绍及使用"></a>OAuth2 介绍及使用</h2><h3 id="名词定义"><a href="#名词定义" class="headerlink" title="名词定义"></a>名词定义</h3><ul>
<li>Third-party application:第三方应用程序,本文中又称”客户端”(client),即上一节例子中的”云冲印”。</li>
<li>HTTP service:HTTP服务提供商,本文中简称”服务提供商”,即上一节例子中的Google。</li>
<li>Resource Owner:资源所有者,本文中又称”用户”(user)。</li>
<li>User Agent:用户代理,本文中就是指浏览器。</li>
<li>Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。</li>
<li>Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。</li>
</ul>
<h3 id="OAuth的思路"><a href="#OAuth的思路" class="headerlink" title="OAuth的思路"></a>OAuth的思路</h3><p>OAuth在”客户端”与”服务提供商”之间,设置了一个授权层(authorization layer)。”客户端”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端”登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。<br>“客户端”登录授权层以后,”服务提供商”根据令牌的权限范围和有效期,向”客户端”开放用户储存的资料。</p>
<h3 id="运行流程"><a href="#运行流程" class="headerlink" title="运行流程"></a>运行流程</h3><ul>
<li>A 用户打开客户端以后,客户端要求用户给予授权。</li>
<li>B 用户同意给予客户端授权。</li>
<li>C 客户端使用上一步获得的授权,向认证服务器申请令牌。</li>
<li>D 认证服务器对客户端进行认证以后,确认无误,同意发放令牌。</li>
<li>E 客户端使用令牌,向资源服务器申请获取资源。</li>
<li>F 资源服务器确认令牌无误,同意向客户端开放资源。</li>
</ul>
<h3 id="客户端的授权模式"><a href="#客户端的授权模式" class="headerlink" title="客户端的授权模式"></a>客户端的授权模式</h3><p>客户端必须得到用户的授权(authorization grant), 才能获得令牌(access token)</p>
<p>OAuth 2.0定义了四种授权方式:</p>
<ol>
<li>授权码模式 (authorization code)</li>
<li>简化模式 (implicit)</li>
<li>密码模式 (resource owner password credentials)</li>
<li>客户端模式 (client credentials)</li>
</ol>
<h3 id="验证token接口-oauth-check-token"><a href="#验证token接口-oauth-check-token" class="headerlink" title="验证token接口 /oauth/check_token"></a>验证token接口 /oauth/check_token</h3><p>参数 token: 访问令牌</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">http://localhost:8080/oauth/check_token?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDg0NzcyNjgsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiJmZTk1MTNiZS1kZjEwLTQ2MDEtYWM0Yy0zOTc4Njc2NzQ1M2IiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhcHAiXX0.AOWIDfMfqzvMhV1T6eYDVpnvU03vST8XpNY35gNrNnyy_DQcLtFB0n_KIYieG4CYaVGlKnMGGXL7L592Njx6xy3bCdmSHw5_4MZZxxWN9_hj96ZTvi4H5Yc3vVzlXNHIvTTJxUsst15EbmN5s5ZBlZptv_3T70bG6x1iq6sbmMQ8zjHRgSKsZM1hvZ5pAO6BwYmPgIKFDPFtxB36I0LegLypUmzjgUFU5dAfgY-w00yyOf9aNooisM22CmbR0QXg3NAlzeufe_Jjf5W0jBTRzdhnVFreRUeOMMwlyKUb_rXUf53Yx0AXhcEhZYtYaYQpDyGxazcoO_VWSgM89S3nQuf23r20gp-jxgElNvRUnPhbO_jIuOcn2FQlvm-L37FfzlO6cK7BGqegE4ItVERfchznWFPq4Jm98IEKV5mQgAcutrE393uIL2137cNzfdg-DZ5HjFoPeEpiJ_ZL7IydxXgOsVTgYsLZ8Ccl0mO51kOCRC4tRBQxXVS9vtRQtp0TVjGTpdXK7SfLyohK1Ga0TXOA76HZv_nAoKy1BRg-i2bejV900g7-nkQGgthPdZbFk4rylXZe6t8grxCIDgtdYFGNLXiMjmnUYU9MJ35AFc1yGhqjwMX8lzdGsTNPLwNeKq9rt82rZxZuuKpiK3ph4LZgnQX5th6XiNBZDfnRz1M</span><br></pre></td></tr></table></figure>
<p>响应内容 JSON</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"exp"</span>:<span class="number">1508477268</span>,</span><br><span class="line"> <span class="attr">"user_name"</span>:<span class="string">"admin"</span>,</span><br><span class="line"> <span class="attr">"authorities"</span>:[</span><br><span class="line"> <span class="string">"ROLE_ADMIN"</span>,</span><br><span class="line"> <span class="string">"ROLE_USER"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"jti"</span>:<span class="string">"fe9513be-df10-4601-ac4c-39786767453b"</span>,</span><br><span class="line"> <span class="attr">"client_id"</span>:<span class="string">"abc"</span>,</span><br><span class="line"> <span class="attr">"scope"</span>:[</span><br><span class="line"> <span class="string">"all"</span></span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="刷新token接口-oauth-token"><a href="#刷新token接口-oauth-token" class="headerlink" title="刷新token接口 /oauth/token"></a>刷新token接口 /oauth/token</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">curl -d 'grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4MzRmY2FmOS1hOGVhLTQwYWItYjkxMi1jNjU3NDFmOWJkYmMiLCJleHAiOjE1MDg0ODY2MDMsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiNWJkYmEzNTctZTRjOS00MzdjLWE2Y2EtODdhM2FiNGVlZTUzIiwiY2xpZW50X2lkIjoiYWJjIn0.JEttthKb3Laj6iNeMZFvnj_2CHLW5WjzQF0RA5RMyGs&client_id=abc&client_secret=aaaabbbb' "http://localhost:8080/oauth/token"</span><br></pre></td></tr></table></figure>
<p>响应内容 JSON</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"access_token"</span>: <span class="string">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDg0ODY5NzAsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI2ZTQ2Yzg4Yi01YzczLTQxNTktYWY1ZC01YjFlOTVlMGQ2MjIiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhbGwiXX0.3OAl_Tjz-btYjQz5PxrYqc5I1T2iNYPcPrzXeog9eqE"</span>,</span><br><span class="line"> <span class="attr">"token_type"</span>: <span class="string">"bearer"</span>,</span><br><span class="line"> <span class="attr">"refresh_token"</span>: <span class="string">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI2ZTQ2Yzg4Yi01YzczLTQxNTktYWY1ZC01YjFlOTVlMGQ2MjIiLCJleHAiOjE1MDg0ODY2MDMsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiNWJkYmEzNTctZTRjOS00MzdjLWE2Y2EtODdhM2FiNGVlZTUzIiwiY2xpZW50X2lkIjoiYWJjIn0.IvO62nDVawLiVv3oqkjS_P2vlsSntviVltULCcOXQzw"</span>,</span><br><span class="line"> <span class="attr">"expires_in"</span>: <span class="number">3599</span>,</span><br><span class="line"> <span class="attr">"scope"</span>: <span class="string">"all"</span>,</span><br><span class="line"> <span class="attr">"jti"</span>: <span class="string">"6e46c88b-5c73-4159-af5d-5b1e95e0d622"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="授权码模式-authorization-code"><a href="#授权码模式-authorization-code" class="headerlink" title="授权码模式 (authorization code)"></a>授权码模式 (authorization code)</h2><p>授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与”服务提供商”的认证服务器进行互动。</p>
<ul>
<li>A 用户访问客户端,后者将前者导向认证服务器。</li>
<li>B 用户选择是否给予客户端授权。</li>
<li>C 假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。</li>
<li>D 客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。</li>
<li>E 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。</li>
</ul>
<h3 id="获取code接口-oauth-authorize"><a href="#获取code接口-oauth-authorize" class="headerlink" title="获取code接口 /oauth/authorize"></a>获取code接口 /oauth/authorize</h3><p>客户端申请认证的URI,包含以下参数:</p>
<ul>
<li>response_type:表示授权类型,必选项,此处的值固定为”code”</li>
<li>client_id:表示客户端的ID,必选项</li>
<li>redirect_uri:表示重定向URI,可选项</li>
<li>scope:表示申请的权限范围,可选项</li>
<li>state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。</li>
</ul>
<p>服务器回应客户端的URI,包含以下参数:</p>
<ul>
<li>code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。</li>
<li>state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">GET http://localhost:8080/oauth/authorize?client_id=abc&response_type=code&redirect_uri=http://localhost:8080/resources/roles</span><br><span class="line">授权之后 302 http://localhost:8080/resources/roles?code=zmpbg2</span><br></pre></td></tr></table></figure>
<h3 id="获取token接口-oauth-token"><a href="#获取token接口-oauth-token" class="headerlink" title="获取token接口 /oauth/token"></a>获取token接口 /oauth/token</h3><p>D 步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:</p>
<p>_ grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。<br>_ code:表示上一步获得的授权码,必选项。<br>_ redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。<br>_ client_id:表示客户端ID,必选项。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">curl -d 'grant_type=authorization_code&redirect_uri=http://localhost:8080/resources/roles&client_id=abc&client_secret=aaaabbbb&code=zmpbg2' "http://localhost:8080/oauth/token"</span><br></pre></td></tr></table></figure>
<p>Response Header content-type: application/json;charset=UTF-8</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"access_token"</span>: <span class="string">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDg0ODEzOTAsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiIwMDE3NGQyNC1jNWVhLTRhN2ItOTYxZS1jNDdkN2Y4YTNhYmQiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhbGwiXX0.cCAJ-r1qARUQCYjwLswWhui7g48lD_MPaarkPyIZMkM"</span>,</span><br><span class="line"> <span class="attr">"token_type"</span>: <span class="string">"bearer"</span>,</span><br><span class="line"> <span class="attr">"refresh_token"</span>: <span class="string">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiIwMDE3NGQyNC1jNWVhLTRhN2ItOTYxZS1jNDdkN2Y4YTNhYmQiLCJleHAiOjE1MDg0ODEzOTAsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiMTZjMmNiNzktNDE5My00Y2ZiLWJhODgtZGMyMDU5MzRiNGEzIiwiY2xpZW50X2lkIjoiYWJjIn0.mAZSTghSNFEaoV22jwOQjeD35KVa6Sb-zhxYM_ppimE"</span>,</span><br><span class="line"> <span class="attr">"expires_in"</span>: <span class="number">3599</span>,</span><br><span class="line"> <span class="attr">"scope"</span>: <span class="string">"all"</span>,</span><br><span class="line"> <span class="attr">"jti"</span>: <span class="string">"00174d24-c5ea-4a7b-961e-c47d7f8a3abd"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="简化模式-implicit"><a href="#简化模式-implicit" class="headerlink" title="简化模式 (implicit)"></a>简化模式 (implicit)</h2><p>简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了”授权码”这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。</p>
<ul>
<li>A 客户端将用户导向认证服务器。</li>
<li>B 用户决定是否给于客户端授权。</li>
<li>C 假设用户给予授权,认证服务器将用户导向客户端指定的”重定向URI”,并在URI的Hash部分包含了访问令牌。</li>
<li>D 浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。</li>
<li>E 资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。</li>
<li>F 浏览器执行上一步获得的脚本,提取出令牌。</li>
<li>G 浏览器将令牌发给客户端。</li>
</ul>
<h3 id="获取token接口-oauth-token-1"><a href="#获取token接口-oauth-token-1" class="headerlink" title="获取token接口 /oauth/token"></a>获取token接口 /oauth/token</h3><p>A步骤中,客户端发出的HTTP请求,包含以下参数:</p>
<ul>
<li>response_type:表示授权类型,此处的值固定为”token”,必选项。</li>
<li>client_id:表示客户端的ID,必选项。</li>
<li>redirect_uri:表示重定向的URI,可选项。</li>
<li>scope:表示权限范围,可选项。</li>
<li>state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。</li>
</ul>
<p>C步骤中,认证服务器回应客户端的URI,包含以下参数:</p>
<ul>
<li>access_token:表示访问令牌,必选项。</li>
<li>token_type:表示令牌类型,该值大小写不敏感,必选项。</li>
<li>expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。</li>
<li>scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。</li>
<li>state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">http://localhost:8080/oauth/authorize?client_id=abc&response_type=token&redirect_uri=http://localhost:8080/resources/roles</span><br><span class="line">授权之后 302</span><br><span class="line">http://localhost:8080/resources/roles#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDg0Nzc3MDIsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI4ZTA2ZGFlZi0xMGVkLTQ3ZmYtOWY3Ni04ZjM2YmFmNDNiNjciLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhcHAiXX0.7ygi5Z64CvZnjThSf2IlZ1PdLkOOTPQbiqXYrbhg8wM&token_type=bearer&expires_in=3599&scope=app&jti=8e06daef-10ed-47ff-9f76-8f36baf43b67</span><br></pre></td></tr></table></figure>
<h2 id="密码模式-resource-owner-password-credentials"><a href="#密码模式-resource-owner-password-credentials" class="headerlink" title="密码模式 (resource owner password credentials)"></a>密码模式 (resource owner password credentials)</h2><p>密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向”服务商提供商”索要授权。<br>在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。</p>
<ul>
<li>A 用户向客户端提供用户名和密码。</li>
<li>B 客户端将用户名和密码发给认证服务器,向后者请求令牌。</li>
<li>C 认证服务器确认无误后,向客户端提供访问令牌。</li>
</ul>
<h3 id="获取token接口-oauth-token-2"><a href="#获取token接口-oauth-token-2" class="headerlink" title="获取token接口 /oauth/token"></a>获取token接口 /oauth/token</h3><p>B步骤中,客户端发出的HTTP请求,包含以下参数:</p>
<ul>
<li>grant_type:表示授权类型,此处的值固定为”password”,必选项。</li>
<li>username:表示用户名,必选项。</li>
<li>password:表示用户的密码,必选项。</li>
<li>scope:表示权限范围,可选项。</li>