-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
3120 lines (2942 loc) · 206 KB
/
index.html
File metadata and controls
3120 lines (2942 loc) · 206 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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>IdeaBook - </title>
<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
code > span.dt { color: #902000; } /* DataType */
code > span.dv { color: #40a070; } /* DecVal */
code > span.bn { color: #40a070; } /* BaseN */
code > span.fl { color: #40a070; } /* Float */
code > span.ch { color: #4070a0; } /* Char */
code > span.st { color: #4070a0; } /* String */
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
code > span.ot { color: #007020; } /* Other */
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
code > span.fu { color: #06287e; } /* Function */
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
code > span.cn { color: #880000; } /* Constant */
code > span.sc { color: #4070a0; } /* SpecialChar */
code > span.vs { color: #4070a0; } /* VerbatimString */
code > span.ss { color: #bb6688; } /* SpecialString */
code > span.im { } /* Import */
code > span.va { color: #19177c; } /* Variable */
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code > span.op { color: #666666; } /* Operator */
code > span.bu { } /* BuiltIn */
code > span.ex { } /* Extension */
code > span.pp { color: #bc7a00; } /* Preprocessor */
code > span.at { color: #7d9029; } /* Attribute */
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
</style>
<link rel="stylesheet" href="style.css">
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
<meta name="viewport" content="width=device-width">
</head>
<body>
<p>
<h1>IdeaBook</h1>
<p>Phodal</p>
</p>
<div style="width:800px">
<nav id="TOC">
<ul>
<li><a href="#svg与svgwrite">SVG与SVGWrite</a></li>
<li><a href="#高级badge">高级Badge</a></li>
<li><a href="#最后代码">最后代码</a></li>
<li><a href="#系统架构">系统架构</a><ul>
<li><a href="#用户场景">用户场景</a></li>
</ul></li>
<li><a href="#builder-构建工具">Builder: 构建工具</a></li>
<li><a href="#code-静态页面生成">Code: 静态页面生成</a></li>
<li><a href="#编辑-发布-开发分离">编辑-发布-开发分离</a></li>
<li><a href="#从schema到数据库">从Schema到数据库</a></li>
<li><a href="#git作为nosql数据库">git作为NoSQL数据库</a><ul>
<li><a href="#git-json文件">git + JSON文件</a></li>
</ul></li>
<li><a href="#solr">Solr</a></li>
<li><a href="#gmap-solr-polygon-搜索实战">Gmap Solr Polygon 搜索实战</a><ul>
<li><a href="#solr-flask">Solr Flask</a></li>
<li><a href="#google-map-polygon">Google map Polygon</a></li>
</ul></li>
<li><a href="#growth">Growth</a><ul>
<li><a href="#从web到混合应用再到桌面应用">从Web到混合应用,再到桌面应用</a></li>
<li><a href="#响应式设计">响应式设计</a></li>
<li><a href="#平台特定代码">平台特定代码</a><ul>
<li><a href="#存储">存储</a></li>
<li><a href="#数据分析">数据分析</a></li>
<li><a href="#更新">更新</a></li>
<li><a href="#桌面应用">桌面应用</a></li>
</ul></li>
<li><a href="#未来">未来</a></li>
<li><a href="#构架设计">构架设计</a><ul>
<li><a href="#gis架构说明-服务端">GIS架构说明 —— 服务端</a></li>
<li><a href="#haystack">Haystack</a></li>
<li><a href="#elasticsearch">ElasticSearch</a></li>
<li><a href="#gis架构说明-客户端">GIS架构说明 —— 客户端</a></li>
<li><a href="#ionic">Ionic</a></li>
<li><a href="#django-rest-framework">Django REST Framework</a></li>
</ul></li>
<li><a href="#其他">其他</a></li>
<li><a href="#django-gis准备">Django GIS准备</a></li>
<li><a href="#配置django">配置Django</a><ul>
<li><a href="#配置haystack">配置Haystack</a></li>
<li><a href="#配置django-1">配置Django</a></li>
</ul></li>
<li><a href="#其他-1">其他:</a><ul>
<li><a href="#django-haystack-model创建">Django Haystack Model创建</a></li>
<li><a href="#创建search_index">创建search_index</a></li>
</ul></li>
<li><a href="#创建数据">创建数据</a><ul>
<li><a href="#测试">测试</a></li>
</ul></li>
<li><a href="#其他-2">其他:</a></li>
<li><a href="#开始之前">开始之前</a></li>
<li><a href="#ionic-elasticsearch-创建页面">Ionic ElasticSearch 创建页面</a></li>
<li><a href="#ionic-elasticsearch-service">Ionic ElasticSearch Service</a></li>
<li><a href="#运行">运行</a></li>
<li><a href="#其他-3">其他:</a></li>
<li><a href="#设计思路">设计思路</a></li>
<li><a href="#openlayer">OpenLayer</a><ul>
<li><a href="#添加openlayer-3">添加OpenLayer 3</a></li>
</ul></li>
<li><a href="#ionic-openlayer-地图显示">Ionic OpenLayer 地图显示:</a><ul>
<li><a href="#创建nsservice">创建NSService</a></li>
<li><a href="#创建基本地图显示">创建基本地图显示</a></li>
<li><a href="#获取当前位置">获取当前位置</a></li>
<li><a href="#获取结果并显示">获取结果并显示</a></li>
<li><a href="#添加点击事件">添加点击事件</a></li>
</ul></li>
<li><a href="#其他-4">其他:</a></li>
<li><a href="#物联网层级结构">物联网层级结构</a></li>
<li><a href="#物联网服务层">物联网服务层</a></li>
<li><a href="#virtual-dom">Virtual DOM</a><ul>
<li><a href="#一个jasmine-jquery测试">一个Jasmine jQuery测试</a></li>
<li><a href="#virtual-dom与hyperscript">virtual-dom与HyperScript</a></li>
</ul></li>
<li><a href="#标记dom变化">标记DOM变化</a><ul>
<li><a href="#其他-5">其他</a></li>
</ul></li>
<li><a href="#构建框架">构建框架</a><ul>
<li><a href="#关于backbone">关于Backbone</a></li>
<li><a href="#其他可替换的框架">其他可替换的框架</a></li>
</ul></li>
<li><a href="#项目">项目</a><ul>
<li><a href="#源码">源码</a></li>
<li><a href="#进展及目的">进展及目的</a></li>
</ul></li>
<li><a href="#其他-6">其他</a></li>
<li><a href="#requirejs-使用">RequireJS 使用</a><ul>
<li><a href="#库及依赖">库及依赖</a></li>
<li><a href="#使用requirejs">使用RequireJS</a></li>
</ul></li>
<li><a href="#开始之前-1">开始之前</a><ul>
<li><a href="#环境准备">环境准备</a></li>
<li><a href="#js库准备">JS库准备</a></li>
<li><a href="#只想要这次的代码">只想要这次的代码</a></li>
</ul></li>
<li><a href="#构建移动web-cms">构建移动web CMS</a><ul>
<li><a href="#添加路由">添加路由</a></li>
<li><a href="#创建主页view">创建主页View</a></li>
</ul></li>
<li><a href="#requirejs与jquery-插件示例">RequireJS与jQuery 插件示例</a></li>
<li><a href="#墨颀cms添加jquery插件"><a href="http://cms.moqi.mobi">墨颀CMS</a>添加jQuery插件</a><ul>
<li><a href="#jquery-sidr">jQuery Sidr</a></li>
<li><a href="#jquery-sidr与requirejs协作">jQuery Sidr与RequireJS协作</a></li>
</ul></li>
<li><a href="#django-tastypie-跨域">Django Tastypie 跨域</a><ul>
<li><a href="#django-tastypie示例">Django Tastypie示例</a></li>
<li><a href="#django-tastypie-跨域支持">Django Tastypie 跨域支持</a></li>
</ul></li>
<li><a href="#django-与墨颀cms整合">Django 与墨颀CMS整合</a></li>
<li><a href="#requirejs-plugins">RequireJS Plugins</a></li>
<li><a href="#requirejs-json文件加载">RequireJS JSON文件加载</a></li>
<li><a href="#简单的博客构成">简单的博客构成</a><ul>
<li><a href="#博文列表">博文列表</a></li>
</ul></li>
<li><a href="#移动cms-获取在线数据">移动CMS 获取在线数据</a><ul>
<li><a href="#其他-7">其他</a></li>
</ul></li>
<li><a href="#获取每篇博客">获取每篇博客</a></li>
<li><a href="#添加博文的路由">添加博文的路由</a><ul>
<li><a href="#backbone路由参数">Backbone路由参数</a></li>
</ul></li>
<li><a href="#墨颀cms-重构">墨颀CMS 重构</a></li>
<li><a href="#构建函数">构建函数</a></li>
<li><a href="#移动cms滑动">移动CMS滑动</a><ul>
<li><a href="#添加jquery-touchwipe">添加jQuery Touchwipe</a></li>
</ul></li>
</ul></li>
<li><a href="#oculus-node.js-three.js-打造vr世界">Oculus + Node.js + Three.js 打造VR世界</a><ul>
<li><a href="#node-oculus-services">Node Oculus Services</a><ul>
<li><a href="#安装node-nmd">安装Node NMD</a></li>
<li><a href="#node.js-oculus-helloworld">Node.js Oculus Hello,World</a></li>
<li><a href="#node-oculus-websocket">Node Oculus WebSocket</a></li>
</ul></li>
<li><a href="#three.js-oculus-effect-dk2-control">Three.js + Oculus Effect + DK2 Control</a></li>
<li><a href="#three.js-dk2controls">Three.js DK2Controls</a><ul>
<li><a href="#欧拉角与四元数">欧拉角与四元数</a></li>
<li><a href="#three.js-dk2controls-1">Three.js DK2Controls</a></li>
<li><a href="#three.js-keyhandler">Three.js KeyHandler</a></li>
</ul></li>
<li><a href="#结语">结语</a></li>
<li><a href="#exif">EXIF</a><ul>
<li><a href="#exifread">ExifRead</a></li>
<li><a href="#exifread安装">ExifRead安装</a></li>
<li><a href="#exifread-exif.py">ExifRead Exif.py</a></li>
</ul></li>
<li><a href="#cartodb">CartoDB</a><ul>
<li><a href="#简介">简介</a></li>
</ul></li>
<li><a href="#打造自己的照片地图">打造自己的照片地图</a><ul>
<li><a href="#python-遍历文件">python 遍历文件</a></li>
<li><a href="#python-解析照片信息">python 解析照片信息</a></li>
<li><a href="#python-提取照片信息生成文件">python 提取照片信息生成文件</a></li>
<li><a href="#上传到cartodb">上传到cartodb</a></li>
<li><a href="#从零开始设计技能树-使用graphviz建立模型">从零开始设计技能树: 使用Graphviz建立模型</a></li>
</ul></li>
<li><a href="#graphviz">Graphviz</a></li>
<li><a href="#简单的技能树">简单的技能树</a></li>
</ul></li>
<li><a href="#技能树之旅-计算点数与从这开始">技能树之旅: 计算点数与从这开始</a><ul>
<li><a href="#从这开始">从这开始</a><ul>
<li><a href="#从knockout开始">从Knockout开始</a></li>
</ul></li>
<li><a href="#点数计算">点数计算</a></li>
<li><a href="#其他-8">其他</a></li>
<li><a href="#tooltipster">Tooltipster</a><ul>
<li><a href="#d3.js-tooltipster-require配置">D3.js Tooltipster Require配置</a></li>
<li><a href="#整合代码">整合代码</a></li>
</ul></li>
<li><a href="#结束">结束</a></li>
<li><a href="#需求说明">需求说明</a></li>
<li><a href="#python-文字转logo实战">Python 文字转Logo实战</a><ul>
<li><a href="#基础代码">基础代码</a></li>
<li><a href="#圆角代码">圆角代码</a></li>
<li><a href="#颜色配置">颜色配置</a></li>
</ul></li>
<li><a href="#结束-1">结束</a></li>
<li><a href="#离线地图与搜索">离线地图与搜索</a><ul>
<li><a href="#地图离线">地图离线</a></li>
<li><a href="#多边形搜索">多边形搜索</a></li>
</ul></li>
<li><a href="#从地点到地图上显示">从地点到地图上显示</a></li>
<li><a href="#从地图到地点上显示">从地图到地点上显示</a></li>
<li><a href="#demo">Demo</a></li>
</ul></li>
</ul>
</nav>
<p>前几天,再次看到一些CI的Badge的时候,就想着要做一个自己的Badge:</p>
<figure>
<img src="/static/media/uploads/node.png" alt="Badge" /><figcaption>Badge</figcaption>
</figure>
<p>接着,我就找了个图形工具简单地先设计了下面的一个Badge:</p>
<figure>
<img src="/static/media/uploads/refactor.png" alt="Demo" /><figcaption>Demo</figcaption>
</figure>
<p>生成的格式是SVG,接着我就打开SVG看看里面发现了什么。</p>
<div class="sourceCode"><pre class="sourceCode xml"><code class="sourceCode xml"><span class="kw"><?xml</span> version="1.0" encoding="UTF-8" standalone="no"<span class="kw">?></span>
<span class="kw"><svg</span><span class="ot"> width=</span><span class="st">"1006px"</span><span class="ot"> height=</span><span class="st">"150px"</span><span class="ot"> viewBox=</span><span class="st">"0 0 1006 150"</span><span class="ot"> version=</span><span class="st">"1.1"</span><span class="ot"> xmlns=</span><span class="st">"http://www.w3.org/2000/svg"</span><span class="ot"> xmlns:xlink=</span><span class="st">"http://www.w3.org/1999/xlink"</span><span class="kw">></span>
<span class="co"><!-- Generator: Sketch 3.7 (28169) - http://www.bohemiancoding.com/sketch --></span>
<span class="kw"><title></span>phodal<span class="kw"></title></span>
<span class="kw"><desc></span>Created with Sketch.<span class="kw"></desc></span>
<span class="kw"><defs></defs></span>
<span class="kw"><g</span><span class="ot"> id=</span><span class="st">"Page-1"</span><span class="ot"> stroke=</span><span class="st">"none"</span><span class="ot"> stroke-width=</span><span class="st">"1"</span><span class="ot"> fill=</span><span class="st">"none"</span><span class="ot"> fill-rule=</span><span class="st">"evenodd"</span><span class="kw">></span>
<span class="kw"><rect</span><span class="ot"> id=</span><span class="st">"Rectangle-1"</span><span class="ot"> fill=</span><span class="st">"#5E6772"</span><span class="ot"> style=</span><span class="st">"mix-blend-mode: hue;"</span><span class="ot"> x=</span><span class="st">"0"</span><span class="ot"> y=</span><span class="st">"0"</span><span class="ot"> width=</span><span class="st">"640"</span><span class="ot"> height=</span><span class="st">"150"</span><span class="kw">></rect></span>
<span class="kw"><rect</span><span class="ot"> id=</span><span class="st">"Rectangle-1"</span><span class="ot"> fill=</span><span class="st">"#2196F3"</span><span class="ot"> style=</span><span class="st">"mix-blend-mode: hue;"</span><span class="ot"> x=</span><span class="st">"640"</span><span class="ot"> y=</span><span class="st">"0"</span><span class="ot"> width=</span><span class="st">"366"</span><span class="ot"> height=</span><span class="st">"150"</span><span class="kw">></rect></span>
<span class="kw"><text</span><span class="ot"> id=</span><span class="st">"PHODAL"</span><span class="ot"> font-family=</span><span class="st">"Helvetica"</span><span class="ot"> font-size=</span><span class="st">"120"</span><span class="ot"> font-weight=</span><span class="st">"normal"</span><span class="ot"> fill=</span><span class="st">"#FFFFFF"</span><span class="kw">></span>
<span class="kw"><tspan</span><span class="ot"> x=</span><span class="st">"83"</span><span class="ot"> y=</span><span class="st">"119"</span><span class="kw">></span>PHODAL<span class="kw"></tspan></span>
<span class="kw"></text></span>
<span class="kw"><text</span><span class="ot"> id=</span><span class="st">"idea"</span><span class="ot"> font-family=</span><span class="st">"Helvetica"</span><span class="ot"> font-size=</span><span class="st">"120"</span><span class="ot"> font-weight=</span><span class="st">"normal"</span><span class="ot"> fill=</span><span class="st">"#FFFFFF"</span><span class="kw">></span>
<span class="kw"><tspan</span><span class="ot"> x=</span><span class="st">"704"</span><span class="ot"> y=</span><span class="st">"122"</span><span class="kw">></span>idea<span class="kw"></tspan></span>
<span class="kw"></text></span>
<span class="kw"></g></span>
<span class="kw"></svg></span></code></pre></div>
<p>看了看代码很简单,我就想这可以用代码生成——我就可以生成出不同的样子了。</p>
<h2 id="svg与svgwrite">SVG与SVGWrite</h2>
<p>SVG就是一个XML</p>
<blockquote>
<p>可缩放矢量图形(Scalable Vector Graphics,SVG) ,是一种用来描述二维矢量图形的XML 标记语言。</p>
</blockquote>
<p>要对这个XML进行修改也是一件很容易的事。只是,先找了PIL发现不支持,就找到了一个名为SVGWrite的工具。</p>
<blockquote>
<p>A Python library to create SVG drawings.</p>
</blockquote>
<p>示例代码如下:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="im">import</span> svgwrite
dwg <span class="op">=</span> svgwrite.Drawing(<span class="st">'test.svg'</span>, profile<span class="op">=</span><span class="st">'tiny'</span>)
dwg.add(dwg.line((<span class="dv">0</span>, <span class="dv">0</span>), (<span class="dv">10</span>, <span class="dv">0</span>), stroke<span class="op">=</span>svgwrite.rgb(<span class="dv">10</span>, <span class="dv">10</span>, <span class="dv">16</span>, <span class="st">'%'</span>)))
dwg.add(dwg.text(<span class="st">'Test'</span>, insert<span class="op">=</span>(<span class="dv">0</span>, <span class="fl">0.2</span>)))
dwg.save()</code></pre></div>
<p>然后我就照猫画虎地写了一个:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="im">import</span> svgwrite
dwg <span class="op">=</span> svgwrite.Drawing(<span class="st">'idea.svg'</span>, profile<span class="op">=</span><span class="st">'full'</span>, size<span class="op">=</span>(<span class="st">u'1006'</span>, <span class="st">u'150'</span>))
shapes <span class="op">=</span> dwg.add(dwg.g(<span class="bu">id</span><span class="op">=</span><span class="st">'shapes'</span>, fill<span class="op">=</span><span class="st">'none'</span>))
shapes.add(dwg.rect((<span class="dv">0</span>, <span class="dv">0</span>), (<span class="dv">640</span>, <span class="dv">150</span>), fill<span class="op">=</span><span class="st">'#5E6772'</span>))
shapes.add(dwg.rect((<span class="dv">640</span>, <span class="dv">0</span>), (<span class="dv">366</span>, <span class="dv">150</span>), fill<span class="op">=</span><span class="st">'#2196F3'</span>))
shapes.add(dwg.text(<span class="st">'PHODAL'</span>, insert<span class="op">=</span>(<span class="dv">83</span>, <span class="dv">119</span>), fill<span class="op">=</span><span class="st">'#FFFFFF'</span>,font_size<span class="op">=</span><span class="dv">120</span>, font_family<span class="op">=</span><span class="st">'Helvetica'</span>))
shapes.add(dwg.text(<span class="st">'idea'</span>, insert<span class="op">=</span>(<span class="dv">704</span>, <span class="dv">122</span>), fill<span class="op">=</span><span class="st">'#FFFFFF'</span>, font_size<span class="op">=</span><span class="dv">120</span>, font_family<span class="op">=</span><span class="st">'Helvetica'</span>))
dwg.save()</code></pre></div>
<p>发现和上面的样式几乎是一样的,就顺手做了剩下的几个。然后想了想,我这样做都一样,一点都不好看。</p>
<h2 id="高级badge">高级Badge</h2>
<p>第一眼看到</p>
<figure>
<img src="/static/media/uploads/clean_code.png" alt="Idea Prototype" /><figcaption>Idea Prototype</figcaption>
</figure>
<p>我就想着要不和这个一样好了,不就是画几条线的事么。</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python">
<span class="kw">def</span> draw_for_bg_plus():
<span class="cf">for</span> x <span class="op">in</span> <span class="bu">range</span>(y_text_split <span class="op">+</span> rect_length, width, rect_length):
shapes.add(dwg.line((x, <span class="dv">0</span>), (x, height), stroke<span class="op">=</span><span class="st">'#EEEEEE'</span>, stroke_opacity<span class="op">=</span><span class="fl">0.3</span>))
<span class="cf">for</span> y <span class="op">in</span> <span class="bu">range</span>(rect_length, height, rect_length):
shapes.add(dwg.line((y_text_split, y), (width, y), stroke<span class="op">=</span><span class="st">'#EEEEEE'</span>, stroke_opacity<span class="op">=</span><span class="fl">0.3</span>))
<span class="cf">for</span> x <span class="op">in</span> <span class="bu">range</span>(y_text_split <span class="op">+</span> max_rect_length, width, max_rect_length):
<span class="cf">for</span> y <span class="op">in</span> <span class="bu">range</span>(<span class="dv">0</span>, height, max_rect_length):
shapes.add(dwg.line((x, y <span class="op">-</span> <span class="dv">4</span>), (x, y <span class="op">+</span> <span class="dv">4</span>), stroke<span class="op">=</span><span class="st">'#EEEEEE'</span>, stroke_width<span class="op">=</span><span class="st">'2'</span>, stroke_opacity<span class="op">=</span><span class="fl">0.4</span>))
<span class="cf">for</span> y <span class="op">in</span> <span class="bu">range</span>(<span class="dv">0</span>, height, max_rect_length):
<span class="cf">for</span> x <span class="op">in</span> <span class="bu">range</span>(y_text_split <span class="op">+</span> max_rect_length, width, max_rect_length):
shapes.add(dwg.line((x <span class="op">-</span> <span class="dv">4</span>, y), (x <span class="op">+</span> <span class="dv">4</span>, y), stroke<span class="op">=</span><span class="st">'#EEEEEE'</span>, stroke_width<span class="op">=</span><span class="st">'2'</span>, stroke_opacity<span class="op">=</span><span class="fl">0.4</span>))
draw_for_bg_plus()</code></pre></div>
<p>就有了下面的图,于是我又按照这种感觉来了好几下</p>
<figure>
<img src="/static/media/uploads/tdd.png" alt="Finally" /><figcaption>Finally</figcaption>
</figure>
<h2 id="最后代码">最后代码</h2>
<p>GitHub: <a href="https://github.com/phodal/brand" class="uri">https://github.com/phodal/brand</a></p>
<p>尽管没有特别的动力去构建一个全新的CMS,但是我还是愿意去撰文一篇来书写如何去做这样的事——编辑-发布-开发分离模式是如何工作的。微服务是我们对于复杂应用的一种趋势,编辑-发布-开发分离模式则是另外一种趋势。在上篇文章《<a href="https://github.com/phodal/repractise/blob/gh-pages/chapters/refactor-cms.md">Repractise架构篇一: CMS的重构与演进</a>》中,我们说到编辑-发布-开发分离模式。</p>
<h2 id="系统架构">系统架构</h2>
<p>如先前提到的,Carrot使用了下面的方案来搭建他们的静态内容的CMS。</p>
<figure>
<img src="/static/media/uploads/node.png" alt="Carrot" /><figcaption>Carrot</figcaption>
</figure>
<p>在这个方案里内容是用Contentful来发布他们的内容。而在我司<a href="https://www.thoughtworks.com/">ThoughtWorks</a>的官网里则采用了Github来管理这些内容。于是如果让我们写一个基于Github的CMS,那么架构变成了这样:</p>
<figure>
<img src="/static/media/uploads/refactor.png" alt="Github 编辑-发布-开发" /><figcaption>Github 编辑-发布-开发</figcaption>
</figure>
<p>或许你也用过Hexo / Jekyll / Octopress这样的静态博客,他们的原理都是类似的。我们有一个代码库用于生成静态页面,然后这些静态页面会被PUSH到Github Pages上。</p>
<p>从我们设计系统的角度来说,我们会在Github上有三个代码库:</p>
<ol type="1">
<li>Content。用于存放编辑器生成的JSON文件,这样我们就可以GET这些资源,并用Backbone / Angular / React 这些前端框架来搭建SPA。</li>
<li>Code。开发者在这里存放他们的代码,如主题、静态文件生成器、资源文件等等。</li>
<li>Builder。在这里它是运行于Travis CI上的一些脚本文件,用于Clone代码,并执行Code中的脚本。</li>
</ol>
<p>以及一些额外的服务,当且仅当你有一些额外的功能需求的时候。</p>
<ol type="1">
<li>Extend Service。当我们需要搜索服务时,我们就需要这样的一些服务。如我正考虑使用Python的whoosh来完成这个功能,这时候我计划用Flask框架,但是只是计划中——因为没有合适的中间件。</li>
<li>Editor。相比于前面的那些知识这一步适合更重要,也就是为什么生成的格式是JSON而不是Markdown的原理。对于非程序员来说,要熟练掌握Markdown不是一件容易的事。于是,一个考虑中的方案就是使用 Electron + Node.js来生成API,最后通过GitHub API V3来实现上传。</li>
</ol>
<p>So,这一个过程是如何进行的。</p>
<h3 id="用户场景">用户场景</h3>
<p>整个过程的Pipeline如下所示:</p>
<ol type="1">
<li>编辑使用他们的编辑器来编辑的内容并点击发布,然后这个内容就可以通过GitHub API上传到Content这个Repo里。</li>
<li>这时候需要有一个WebHooks监测到了Content代码库的变化,便运行Builder这个代码库的Travis CI。</li>
<li>这个Builder脚本首先,会设置一些基本的git配置。然后clone Content和Code的代码,接着运行构建命令,生成新的内容。</li>
<li>然后Builder Commit内容,并PUSH内容。</li>
</ol>
<p>这里还依赖于WebHook这个东西——还没想到一个合适的解决方案。下面,我们对里面的内容进行一些拆解,Content里面由于是JSON就不多解释了。</p>
<h2 id="builder-构建工具">Builder: 构建工具</h2>
<p>Github与Travis之间,可以做一个自动部署的工具。相信已经有很多人在Github上玩过这样的东西——先在Github上生成Token,然后用travis加密:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="kw">travis</span> encrypt-file ssh_key --add</code></pre></div>
<p>加密后的Key就会保存到<code>.travis.yml</code>文件里,然后就可以在Travis CI上push你的代码到Github上了。</p>
<p>接着,你需要创建个deploy脚本,并且在<code>after_success</code>执行它:</p>
<pre class="yml"><code>after_success:
- test $TRAVIS_PULL_REQUEST == "false" && test $TRAVIS_BRANCH == "master" && bash deploy.sh</code></pre>
<p>在这个脚本里,你所需要做的就是clone content和code中的代码,并执行code中的生成脚本,生成新的内容后,提交代码。</p>
<pre><code>#!/bin/bash
set -o errexit -o nounset
rev=$(git rev-parse --short HEAD)
cd stage/
git init
git config user.name "Robot"
git config user.email "robot@phodal.com"
git remote add upstream "https://$GH_TOKEN@github.com/phodal-archive/echeveria-deploy.git"
git fetch upstream
git reset upstream/gh-pages
git clone https://github.com/phodal-archive/echeveria-deploy code
git clone https://github.com/phodal-archive/echeveria-content content
pwd
cp -a content/contents code/content
cd code
npm install
npm install grunt-cli -g
grunt
mv dest/* ../
cd ../
rm -rf code
rm -rf content
touch .
if [ ! -f CNAME ]; then
echo "deploy.baimizhou.net" > CNAME
fi
git add -A .
git commit -m "rebuild pages at ${rev}"
git push -q upstream HEAD:gh-pages</code></pre>
<p>这就是这个builder做的事情——其中最主要的一个任务是<code>grunt</code>,它所做的就是:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">grunt</span>.<span class="at">registerTask</span>(<span class="st">'default'</span><span class="op">,</span> [<span class="st">'clean'</span><span class="op">,</span> <span class="st">'assemble'</span><span class="op">,</span> <span class="st">'copy'</span>])<span class="op">;</span></code></pre></div>
<h2 id="code-静态页面生成">Code: 静态页面生成</h2>
<p>Assemble是一个使用Node.js,Grunt.js,Gulp,Yeoman 等来实现的静态网页生成系统。这样的生成器有很多,Zurb Foundation, Zurb Ink, Less.js / lesscss.org, Topcoat, Web Experience Toolkit等组织都使用这个工具来生成。这个工具似乎上个Release在一年多以前,现在正在开始0.6。虽然,这并不重要,但是还是顺便一说。</p>
<p>我们所要做的就是在我们的<code>Gruntfile.js</code>中写相应的生成代码。</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> assemble<span class="op">:</span> <span class="op">{</span>
<span class="dt">options</span><span class="op">:</span> <span class="op">{</span>
<span class="dt">flatten</span><span class="op">:</span> <span class="kw">true</span><span class="op">,</span>
<span class="dt">partials</span><span class="op">:</span> [<span class="st">'templates/includes/*.hbs'</span>]<span class="op">,</span>
<span class="dt">layoutdir</span><span class="op">:</span> <span class="st">'templates/layouts'</span><span class="op">,</span>
<span class="dt">data</span><span class="op">:</span> <span class="st">'content/blogs.json'</span><span class="op">,</span>
<span class="dt">layout</span><span class="op">:</span> <span class="st">'default.hbs'</span>
<span class="op">},</span>
<span class="dt">site</span><span class="op">:</span> <span class="op">{</span>
<span class="dt">files</span><span class="op">:</span> <span class="op">{</span><span class="st">'dest/'</span><span class="op">:</span> [<span class="st">'templates/*.hbs'</span>]<span class="op">}</span>
<span class="op">},</span>
<span class="dt">blogs</span><span class="op">:</span> <span class="op">{</span>
<span class="dt">options</span><span class="op">:</span> <span class="op">{</span>
<span class="dt">flatten</span><span class="op">:</span> <span class="kw">true</span><span class="op">,</span>
<span class="dt">layoutdir</span><span class="op">:</span> <span class="st">'templates/layouts'</span><span class="op">,</span>
<span class="dt">data</span><span class="op">:</span> <span class="st">'content/*.json'</span><span class="op">,</span>
<span class="dt">partials</span><span class="op">:</span> [<span class="st">'templates/includes/*.hbs'</span>]<span class="op">,</span>
<span class="dt">pages</span><span class="op">:</span> pages
<span class="op">},</span>
<span class="dt">files</span><span class="op">:</span> [
<span class="op">{</span> <span class="dt">dest</span><span class="op">:</span> <span class="st">'./dest/blog/'</span><span class="op">,</span> <span class="dt">src</span><span class="op">:</span> <span class="st">'!*'</span> <span class="op">}</span>
]
<span class="op">}</span>
<span class="op">}</span></code></pre></div>
<p>配置中的site用于生成页面相关的内容,blogs则可以根据json文件的文件名生成对就的html文件存储到blog目录中。</p>
<p>生成后的目录结果如下图所示:</p>
<pre><code> .
├── about.html
├── blog
│ ├── blog-posts.html
│ └── blogs.html
├── blog.html
├── css
│ ├── images
│ │ └── banner.jpg
│ └── style.css
├── index.html
└── js
├── jquery.min.js
└── script.js
7 directories, 30 files</code></pre>
<p>这里的静态文件内容就是最后我们要发布的内容。</p>
<p>还需要做的一件事情就是:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">grunt</span>.<span class="at">registerTask</span>(<span class="st">'dev'</span><span class="op">,</span> [<span class="st">'default'</span><span class="op">,</span> <span class="st">'connect:server'</span><span class="op">,</span> <span class="st">'watch:site'</span>])<span class="op">;</span></code></pre></div>
<p>用于开发阶段这样的代码就够了,这个和你使用WebPack + React 似乎相差不了多少。</p>
<h2 id="编辑-发布-开发分离">编辑-发布-开发分离</h2>
<p>在这种情形中,编辑能否完成工作就不依赖于网站——脱稿又少了 个借口。这时候网站出错的概率太小了——你不需要一个缓存服务器、HTTP服务器,由于没有动态生成的内容,你也不需要守护进程。这些内容都是静态文件,你可以将他们放在任何可以提供静态文件托管的地方——CloudFront、S3等等。或者你再相信自己的服务器,Nginx可是全球第二好(第一还没出现)的静态文件服务器。</p>
<p>开发人员只在需要的时候去修改网站的一些内容。</p>
<p>So,你可能会担心如果这时候修改的东西有问题了怎么办。</p>
<ol type="1">
<li>使用这种模式就意味着你需要有测试来覆盖这些构建工具、生成工具。</li>
<li>相比于自己的代码,别人的CMS更可靠?</li>
</ol>
<p>需要注意的是如果你上一次构建成功,你生成的文件都是正常的,那么你只需要回滚开发相关的代码即可。旧的代码仍然可以工作得很好。</p>
<p>其次,由于生成的是静态文件,查错的成本就比较低。</p>
<p>最后,重新放上之前的静态文件。</p>
<blockquote>
<p>动态网页是下一个要解决的难题。我们从数据库中读取数据,再用动态去渲染出一个静态页面,并且缓存服务器来缓存这个页面。既然我们都可以用Varnish、Squid这样的软件来缓存页面——表明它们可以是静态的,为什么不考虑直接使用静态网页呢?</p>
</blockquote>
<p>为了实现之前说到的<code>编辑-发布-开发分离</code>的CMS,我还是花了两天的时间打造了一个面向普通用户的编辑器。效果截图如下所示:</p>
<figure>
<img src="/static/media/uploads/node.png" alt="Echeveria Editor" /><figcaption>Echeveria Editor</figcaption>
</figure>
<p>作为一个普通用户,这是一个很简单的软件。除了Electron + Node.js + React作了一个140M左右的软件,尽管打包完只有40M左右 ,但是还是会把用户吓跑的。不过作为一个快速构建的原型已经很不错了——构建速度很快、并且运行良好。</p>
<p>尽管这个界面看上去还是稍微复杂了一下,还在试着想办法将链接名和日期去掉——问题是为什么会有这两个东西?</p>
<h2 id="从schema到数据库">从Schema到数据库</h2>
<p>我们在我们数据库中定义好了Schema——对一个数据库的结构描述。在《<a href="https://www.phodal.com/blog/editing-publishing-coding-seperate/">编辑-发布-开发分离</a> 》一文中我们说到了echeveria-content的一个数据文件如下所示:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> <span class="op">{</span>
<span class="st">"title"</span><span class="op">:</span> <span class="st">"白米粥"</span><span class="op">,</span>
<span class="st">"author"</span><span class="op">:</span> <span class="st">"白米粥"</span><span class="op">,</span>
<span class="st">"url"</span><span class="op">:</span> <span class="st">"baimizhou"</span><span class="op">,</span>
<span class="st">"date"</span><span class="op">:</span> <span class="st">"2015-10-21"</span><span class="op">,</span>
<span class="st">"description"</span><span class="op">:</span> <span class="st">"# Blog post </span><span class="sc">\n</span><span class="st"> > This is an example blog post </span><span class="sc">\n</span><span class="st"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "</span><span class="op">,</span>
<span class="st">"blogpost"</span><span class="op">:</span> <span class="st">"# Blog post </span><span class="sc">\n</span><span class="st"> > This is an example blog post </span><span class="sc">\n</span><span class="st"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </span><span class="sc">\n</span><span class="st"> Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</span>
<span class="op">}</span></code></pre></div>
<p>比起之前的直接生成静态页面这里的数据就是更有意思地一步了,我们从数据库读取数据就是为了生成一个JSON文件。何不直接以JSON的形式存储文件呢?</p>
<p>我们都定义了这每篇文章的基本元素:</p>
<ol type="1">
<li>title</li>
<li>author</li>
<li>date</li>
<li>description</li>
<li>content</li>
<li>url</li>
</ol>
<p>即使我们使用NoSQL我们也很难逃离这种模式。我们定义这些数据,为了在使用的时候更方便。存储这些数据只是这个过程中的一部分,下部分就是取出这些数据并对他们进行过滤,取出我们需要的数据。</p>
<p>Web的骨架就是这么简单,当然APP也是如此。难的地方在于存储怎样的数据,返回怎样的数据。不同的网站存储着不同的数据,如淘宝存储的是商品的信息,Google存储着各种网站的数据——人们需要不同的方式去存储这些数据,为了更好地存储衍生了更多的数据存储方案——于是有了GFS、Haystack等等。运营型网站想尽办法为最后一公里努力着,成长型的网站一直在想着怎样更好的返回数据,从更好的用户体验到机器学习。而数据则是这个过程中不变的东西。</p>
<p>尽管,我已经想了很多办法去尽可能减少元素——在最开始的版本里只有标题和内容。然而为了满足我们在数据库中定义的结构,不得不造出来这么多对于一般用户不友好的字段。如链接名是为了存储的文件名而存在的,即这个链接名在最后会变成文件名:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">repo</span>.<span class="at">write</span>(<span class="st">'master'</span><span class="op">,</span> <span class="st">'contents/'</span> <span class="op">+</span> <span class="va">data</span>.<span class="at">url</span> <span class="op">+</span> <span class="st">'.json'</span><span class="op">,</span> stringifyData<span class="op">,</span> <span class="st">'Robot: add article '</span> <span class="op">+</span> <span class="va">data</span>.<span class="at">title</span><span class="op">,</span> options<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> data) <span class="op">{</span>
<span class="cf">if</span>(<span class="va">data</span>.<span class="at">commit</span>)<span class="op">{</span>
<span class="va">that</span>.<span class="at">setState</span>(<span class="op">{</span><span class="dt">message</span><span class="op">:</span> <span class="st">"上传成功"</span> <span class="op">+</span> <span class="va">JSON</span>.<span class="at">stringify</span>(data)<span class="op">}</span>)<span class="op">;</span>
<span class="va">that</span>.<span class="va">refs</span>.<span class="va">snackbar</span>.<span class="at">show</span>()<span class="op">;</span>
<span class="va">that</span>.<span class="at">setState</span>(<span class="op">{</span>
<span class="dt">sending</span><span class="op">:</span> <span class="dv">0</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">}</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>然后,上面的数据就会变成一个对象存储到“数据库”中。</p>
<p>今天 ,仍然有很多人用Word、Excel来存储数据。因为对于他们来说,这些软件更为直接,他们简单地操作一下就可以对数据进行排序、筛选。数据以怎样的形式存储并不重要,重要的是他们都以文件的形式存储着。</p>
<h2 id="git作为nosql数据库">git作为NoSQL数据库</h2>
<p>在控制台中运行一下 <code>man git</code>你会得到下面的结果:</p>
<figure>
<img src="/static/media/uploads/refactor.png" alt="Man Git" /><figcaption>Man Git</figcaption>
</figure>
<p>这个答案看起来很有意思——不过这看上去似乎无关主题。</p>
<p>不同的数据库会以不同的形式存储到文件中去。blob是git中最为基本的存储单位,我们的每个content都是一个blob。redis可以以rdb文件的形式存储到文件系统中。完成一个CMS,我们并不需要那么多的查询功能。</p>
<blockquote>
<p>这些上千年的组织机构,只想让人们知道他们想要说的东西。</p>
</blockquote>
<p>我们使用NoSQL是因为:</p>
<ol type="1">
<li>不使用关系模型</li>
<li>在集群中运行良好</li>
<li>开源</li>
<li>无模式</li>
<li>数据交换格式</li>
</ol>
<p>我想其中只有两点对于我来说是比较重要的<code>集群</code>与<code>数据格式</code>。但是集群和数据格式都不是我们要考虑的问题。。。</p>
<p>我们也不存在数据格式的问题、开源的问题,什么问题都没有。。除了,我们之前说到的查询——但是这是可以解决的问题,我们甚至可以返回不同的历史版本的。在这一点上git做得很好,他不会像WordPress那样存储多个版本。</p>
<h3 id="git-json文件">git + JSON文件</h3>
<p>JSON文件 + Nginx就可以变成这样一个合理的API,甚至是运行方式。我们可以对其进行增、删、改、查,尽管就当前来说查需要一个额外的软件来执行,但是为了实现一个用得比较少的功能,而去花费大把的时间可能就是在浪费。</p>
<p>git的“API”提供了丰富的增、删、改功能——你需要commit就可以了。我们所要做的就是:</p>
<ol type="1">
<li>git commit</li>
<li>git push</li>
</ol>
<p>记录一下自己做的一个小东西,当然你也可以在github上找到它:<a href="https://github.com/phodal/gmap-solr" class="uri">https://github.com/phodal/gmap-solr</a></p>
<h2 id="solr">Solr</h2>
<blockquote>
<p>Solr是一个高性能,采用Java5开发,基于Lucene的全文搜索服务器。同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎。</p>
</blockquote>
<p>简单地说: 它是一个搜索引擎</p>
<blockquote>
<p>文档通过Http利用XML 加到一个搜索集合中。查询该集合也是通过http收到一个XML/JSON响应来实现。它的主要特性包括:高效、灵活的缓存功能,垂直搜索功能,高亮显示搜索结果,通过索引复制来提高可用性,提供一套强大Data Schema来定义字段,类型和设置文本分析,提供基于Web的管理界面等。</p>
</blockquote>
<p>即schema.xml</p>
<p><strong>Solr 安装</strong></p>
<pre><code>brew install solr</code></pre>
<h2 id="gmap-solr-polygon-搜索实战">Gmap Solr Polygon 搜索实战</h2>
<p>思路:</p>
<p>用Flask搭建一个简单的servrices,接着在前台用google的api,对后台发出请求。</p>
<h3 id="solr-flask">Solr Flask</h3>
<p>由于,直接调用的是Solr的接口,所以我们的代码显得比较简单:</p>
<pre><code>class All(Resource):
@staticmethod
def get():
base_url = ''
url = (base_url + 'select?q=' + request.query_string + '+&wt=json&indent=true')
result = requests.get(url)
return (result.json()['response']['docs']), 201, {'Access-Control-Allow-Origin': '*'}
api.add_resource(All, '/geo/')</code></pre>
<p>我们在前台需要做的便是,组装geo query。</p>
<h3 id="google-map-polygon">Google map Polygon</h3>
<p>在Google Map的API是支持Polygon搜索的,有对应的一个</p>
<pre><code>google.maps.event.addListener(drawingManager, 'polygoncomplete', renderMarker);</code></pre>
<p>函数来监听,完成<code>polygoncomplete</code>时执行的函数,当我们完成搜索时,便执行<code>renderMarker</code>,在里面做的便是:</p>
<pre><code>$.get('/geo/?' + query, function (results) {
for (var i = 0; i < results.length; i++) {
var location = results[i].geo[0].split(',');
var myLatLng = new google.maps.LatLng(location[0], location[1]);
var title = results[i].title;
marker = new google.maps.Marker({
position: myLatLng,
map: map,
title: title
});
contentString = '<h1>City</h1><br/> address ' + i + '';
google.maps.event.addListener(marker, 'click', (function (marker, contentString, infowindow) {
return function () {
infowindow.setContent(contentString);
infowindow.open(map, marker);
};
})(marker, contentString, infowindow));
}
});</code></pre>
<p>对应的去解析数据,并显示在地图上</p>
<h1 id="growth">Growth</h1>
<p>Web本身就是跨平台的,这意味着这中间存在着无限的可能性。</p>
<p>我是一名Web Developer,对于我来能用Web开发的事情就用Web来完成就好了——不需要编译,不需要等它编译完。我想到哪我就可以写到哪,我改到哪我就可以发生哪发生了变化。</p>
<p>最近我在写Growth——一个帮助开发人员成长的应用,在近一个月的业余时间里,完成了这个应用的:</p>
<ul>
<li>移动应用版:Android、Windows Phone、iOS(等账号和上线)</li>
<li>Web版</li>
<li>桌面版:Mac OS、Windows、GNU/Linux</li>
</ul>
<p>截图合并如下:</p>
<figure>
<img src="/static/media/uploads/growth-full-platforms.jpg" alt="growth-full-platforms.png" /><figcaption>growth-full-platforms.png</figcaption>
</figure>
<p>而更重要的是它们使用了同一份代码——除了对特定设备进行一些处理就没有其他修改。相信全栈的你已经看出来了:</p>
<p>Web = Chrome + Angular.js + Ionic</p>
<p>Desktop = Electron + Angular.js + Ionic</p>
<p>Mobile = Cordova + Angular.js + Ionic</p>
<p>除了前面的WebView不一样,后面都是Angular.js + Ionic。</p>
<h2 id="从web到混合应用再到桌面应用">从Web到混合应用,再到桌面应用</h2>
<p>在最打开的时候它只是一个单纯的混合应用,我想总结一下我的学习经验,分享一下学习的心得,如:</p>
<ul>
<li>完整的Web开发,运维,部署,维护介绍<br />
</li>
<li>如何写好代码——重构、测试、模式</li>
<li>遗留代码、遗留系统的形成</li>
<li>不同阶段所需的技能</li>
<li>书籍推荐</li>
<li>技术栈推荐</li>
<li>Web应用解决方案</li>
</ul>
<p>接着我用Ionic创建了这个应用,这是一个再普通不过的过程。在这个过程里,我一直使用Chrome在调度我的代码。因为我是Android用户,我有Google Play的账号,便发布了Android版本。这时候遇到了一个问题,我并没有Apple Developer账号(现在在申请ing。。),而主要的用户对象程序员,这是一群<strong>不土</strong>的土豪。</p>
<figure>
<img src="/static/media/uploads/iphone.jpg" alt="iPHONE" /><figcaption>iPHONE</figcaption>
</figure>
<p>偶然间我才想到,我只要上传Web版本的代码就可以暂时性实现这个需求了。接着找了个AWS S3的插件,直接上传到了AWS S3上托管成静态文件服务。</p>
<p>几天前在Github上收到一个issue——关于创造桌面版, 我便想着这也是可能的,我只需要写一个启动脚本和编译脚本即可。</p>
<p>所以,最后我们的流程图就如下所示:</p>
<figure>
<img src="/static/media/uploads/growth-arch.png" alt="Growth Arch" /><figcaption>Growth Arch</figcaption>
</figure>
<p>除了显示到VR设备上,好像什么也不缺了。并且在我之前的文章《<a href="https://github.com/phodal/oculus-nodejs-threejs-example">Oculus + Node.js + Three.js 打造VR世界</a>》,也展示了Web在VR世界的可能性。</p>
<p>在这实现期间有几个点可以分享一下:</p>
<ol type="1">
<li>响应式设计</li>
<li>平台/设备特定代码</li>
</ol>
<h2 id="响应式设计">响应式设计</h2>
<p>响应式设计可以主要依赖于Media Query,而响应式设计主要要追随的一点是不同的设备不同的显示,如:</p>
<figure>
<img src="/static/media/uploads/full-platforms.jpg" alt="full-platforms.jpg" /><figcaption>full-platforms.jpg</figcaption>
</figure>
<p>这也意味着,我们需要对不同的设备进行一些处理,如在大的屏幕下,我们需要展示菜单:</p>
<figure>
<img src="/static/media/uploads/gnu-linux.jpg" alt="gnu-linux.png" /><figcaption>gnu-linux.png</figcaption>
</figure>
<p>而这可以依赖于Ionic的<strong>expose-aside-when=“large”</strong>,而并非所有的情形都是这么简单的。如我最近遇到的问题就是图片缩放的问题,之前的图片针对的都是手机版——经过了一定的缩放。</p>
<p>这时在桌面应用上就会出现问题,就需要限定大小等等。</p>
<p>而这个问题相比于平台特定问题则更容易解决。</p>
<h2 id="平台特定代码">平台特定代码</h2>
<p>对于特定平台才有的问题就不是一件容易解决的事,分享一下:</p>
<h3 id="存储">存储</h3>
<p>我遇到的第一个问题是<strong>数据存储</strong>的问题。最开始的时候,我只需要开始混合应用。因此我可以用<strong>Preferences</strong>、或者<strong>SQLite</strong>来存储数据。</p>
<p>后来,我扩展到了Web版,我只好用LocalStoarge。于是,我就开始抽象出一个<strong>$storageServices</strong>来做相应的事。接着遇到一系列的问题,我舍弃了原有的方案,直接使用LocalStoarge。</p>
<h3 id="数据分析">数据分析</h3>
<p>为了开发方便,我使用Google Analytics来分析用户的行为——毕竟数据对我来说也不是特别重要,只要可以看到有人使用就可以了。</p>
<p>这时候遇到的一个问题是,我不需要记录Web用户的行为,但是我希望可以看到有这样的请求发出。于是对于Web用户来说,只需要:</p>
<pre class="js"><code> trackView: function (view) {
console.log(view);
}</code></pre>
<p>而对于手机用户则是:</p>
<pre class="js"><code> trackView: function (view) {
$window.analytics.startTrackerWithId('UA-71907748-1');
$window.analytics.trackView(view)
}</code></pre>
<p>这样在我调试的时候我只需要打个Log,在产品环境时就会Track。</p>
<h3 id="更新">更新</h3>
<p>同样的,对于Android用户来说,他们可以选择自行下载更新,所以我需要针对Android用户有一个自动更新:</p>
<pre><code>var isAndroid = ionic.Platform.isAndroid();
if(isAndroid) {
$updateServices.check('main');
}</code></pre>
<h3 id="桌面应用">桌面应用</h3>
<p>对于桌面应用来说也会有类似的问题,我遇到的第一个问题是Electron默认开启了AMD。于是,直接删之:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><script></span>
<span class="co">//remove module for electron</span>
<span class="cf">if</span>(<span class="kw">typeof</span> module <span class="op">!==</span> <span class="st">'undefined'</span> <span class="op">&&</span> module <span class="op">&&</span> <span class="va">module</span>.<span class="at">exports</span>)<span class="op">{</span>
<span class="kw">delete</span> module<span class="op">;</span>
<span class="op">}</span>
<span class="op"><</span><span class="ss">/script></span></code></pre></div>
<p>类似的问题还有许多,不过由于应用内容的限制,这些问题就没有那么严重了。</p>
<p>如果有一天,我有钱开放这个应用的应用号,那么我就会再次献上这个图:</p>
<figure>
<img src="/static/media/uploads/hexoarch.png" alt="六边形架构" /><figcaption>六边形架构</figcaption>
</figure>
<h2 id="未来">未来</h2>
<p>我就开始思索这个问题,未来的趋势是合并到一起,而这一个趋势在现在就已经是完成时了。</p>
<p>那么未来呢?你觉得会是怎样的?</p>
<p>搜索引擎是个好东西,GIS也是个好东西。当前还有Django和Ionic。最后效果图</p>
<p><img src="/static/media/uploads/node.png" alt="elasticsearch_ionic_map" /> <img src="/static/media/uploads/refactor.png" alt="elasticsearch_ionic_info_page" /></p>
<h2 id="构架设计">构架设计</h2>
<p>对我们的需求进行简要的思考后,设计出了下面的一些简单的架构。</p>
<figure>
<img src="/static/media/uploads/tdd.png" alt="Django ElasticSearch Ionic 架构" /><figcaption>Django ElasticSearch Ionic 架构</figcaption>
</figure>
<h3 id="gis架构说明-服务端">GIS架构说明 —— 服务端</h3>
<p>简单说明:</p>
<ul>
<li>用户在前台或者后台创建数据。</li>
<li>在model保存数据的时候,会调用Google的API解析GPS</li>
<li>在haystack的配置中设置实时更新,当数据创建的时候自动更新索引</li>
<li>数据被ElasticSearch索引</li>
</ul>
<p>下面是框架的一些简单的介绍</p>
<h4 id="django">Django</h4>
<blockquote>
<p><a href="http://www.phodal.com/blog/tag/django/">Django</a> 是一个开放源代码的Web应用框架,由Python写成。采用了MVC的软件设计模式,即模型M,视图V和控制器C。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的。并于2005年7月在BSD许可证下发布。这套框架是以比利时的吉普赛爵士吉他手Django Reinhardt来命名的。</p>
</blockquote>
<blockquote>
<p><a href="http://www.phodal.com/blog/tag/django/">Django</a> 的主要目标是使得开发复杂的、数据库驱动的网站变得简单。Django注重组件的重用性和“可插拔性”,敏捷开发和DRY法则(Don’t Repeat Yourself)。在Django中Python被普遍使用,甚至包括配置文件和数据模型。</p>
</blockquote>
<p>首先考虑Django,而不是其他Node或者Ruby框架的原因是:</p>
<ul>
<li>内置认证系统</li>
<li>内置CSRF</li>
</ul>
<p>当然这是其他框架也所拥有的,主要特性还有:</p>
<ul>
<li>一个表单序列化及验证系统,用于HTML表单和适于数据库存储的数据之间的转换。</li>
<li>一套协助创建地理信息系统(GIS)的基础框架</li>
</ul>
<p>最后一个才是亮点,内置GIS,虽然没怎么用到,但是至少在部署上还是比较方便的。</p>
<h3 id="haystack">Haystack</h3>
<blockquote>
<p>Haystack provides modular search for Django. It features a unified, familiar API that allows you to plug in different search backends (such as Solr, Elasticsearch, Whoosh, Xapian, etc.) without having to modify your code.</p>
</blockquote>
<p>Haystack是为Django提供一个搜索模块blabla..,他的主要特性是可以</p>
<blockquote>
<p>write your search code once and choose the search engine you want it to run on</p>
</blockquote>
<p>也就是说你只需要写你的代码选择你的搜索引擎就可以工作了。</p>
<h3 id="elasticsearch">ElasticSearch</h3>
<p>在上面的Haystack提供了这些一堆的搜索引擎,当然支持地点搜索的只有<code>Solr</code>和<code>ElasticSearch</code>,他们支持的空间搜索有:</p>
<ul>
<li>within<br />
</li>
<li>dwithin</li>
<li>distance<br />
</li>
<li>order_by(‘distance’)<br />
</li>
<li>polygon</li>
</ul>
<p>在文档上没有写Solr的polygon搜索,但是实际上也是支持的(详细见这篇文章: <a href="http://www.phodal.com/blog/google-map-width-solr-use-polygon-search/,用的地图是谷歌,所以需要先学会访问谷歌">google map solr polygon 搜索</a>。</p>
<p>至于为什么用的是ElasticSearch,是因为之前用Solr做过。。。</p>
<h3 id="gis架构说明-客户端">GIS架构说明 —— 客户端</h3>
<h4 id="简单说明-get">简单说明 —— GET</h4>
<ol type="1">
<li>当我们访问Map View的时候,会调用HTML5获取用户的位置</li>
<li>根据用户的位置定位,设置缩放</li>
<li>根据用户的位置发出ElasticSearch请求,返回结果中带上距离</li>
<li>显示</li>
</ol>
<h4 id="简单说明-post">简单说明 —— POST</h4>
<ol type="1">
<li>用户填写数据会发给Django API,并验证</li>
<li>成功时,存入数据库,更新索引。</li>
</ol>
<h3 id="ionic">Ionic</h3>
<blockquote>
<p>Ionic提供了一个免费且开源的移动优化HTML,CSS和JS组件库,来构建高交互性应用。基于Sass构建和AngularJS 优化。</p>
</blockquote>
<p>用到的主要是AngularJS,之前用他写过三个APP。</p>
<h3 id="django-rest-framework">Django REST Framework</h3>
<p>与Django Tastypie相比,DRF的主要优势在于Web界面的调试。</p>
<h2 id="其他">其他</h2>
<p>因为选的是比较熟悉的技术栈,所以也只花了不到两天的业余时间完成的。或许,这也是全栈程序员的优势所在。</p>
<p>服务端代码: <a href="https://github.com/phodal/django-elasticsearch" class="uri">https://github.com/phodal/django-elasticsearch</a></p>
<p>客户端代码: <a href="https://github.com/phodal/ionic-elasticsearch" class="uri">https://github.com/phodal/ionic-elasticsearch</a></p>
<p>下一章: <a href="http://www.phodal.com/blog/django-elasticsearch-haystack-prepare-enviorment/">GIS 移动应用实战 —— Django Haystack ElasticSearch 环境准备</a></p>
<p>在一篇中,我们介绍了 <a href="http://www.phodal.com/blog/django-elasticsearch-ionic-build-gis-application/">《Django ElasticSearch Ionic 打造 GIS 移动应用 —— 架构设计》</a>。接着,我们就开始实战了,内容也很简单。</p>
<h2 id="django-gis准备">Django GIS准备</h2>
<p>1.创建虚拟环境</p>
<pre><code> virtualenv -p /usr/bin/python2.67 django-elasticsearch</code></pre>
<p>2.创建项目</p>
<p>为了方便,这里用的是Mezzanine CMS,相比Django的主要优势是,以后扩展方便。但是对于Django也是可以的。</p>
<p>3.安装依赖</p>
<p>这里我的所有依赖有</p>
<pre><code>django-haystack
Mezzanine==3.1.10
djangorestframework
pygeocoder
elasticsearch</code></pre>
<p>安装</p>
<pre><code>pip install requirements.txt</code></pre>
<p>4.安装ElasticSearch</p>
<p>CentOS</p>
<pre><code>wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.4.2.zip
sudo unzip elasticsearch-1.4.2 -d /usr/local/elasticsearch
rm elasticsearch-1.4.2.zip
cd /usr/local/elasticsearch/elasticsearch-1.4.2/
./bin/plugin install elasticsearch/elasticsearch-cloud-aws/2.4.1
curl -XGET http://localhost:9200</code></pre>
<p>Mac OS</p>
<pre><code>brew install elasticsearch</code></pre>
<p>5.Django Geo环境搭建</p>
<p>CentOS等GNU/Linux系统: 可以参照<a href="http://www.phodal.com/blog/install-geo-django-in-centos/">CentOS Django Geo 环境搭建</a></p>
<p>MacOS: <a href="http://www.phodal.com/blog/django-elasticsearch-geo-solution/">Mac OS Django Geo 环境搭建</a></p>
<h2 id="配置django">配置Django</h2>
<h3 id="配置haystack">配置Haystack</h3>
<pre><code>HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://127.0.0.1:9200/',
'INDEX_NAME': 'haystack',
},
} </code></pre>
<p><code>HAYSTACK_SIGNAL_PROCESSOR</code>是为了可以实时处理。 <code>HAYSTACK_CONNECTIONS</code> 则是配置搜索引擎用的。</p>
<h3 id="配置django-1">配置Django</h3>
<p>在<code>settings.py</code>中的<code>INSTALLED_APPS</code>添加</p>
<pre><code>"haystack",
"rest_framework",</code></pre>
<p>接着</p>
<pre><code> python manage.py createdb
python manage.py migreate</code></pre>
<p>运行</p>
<pre><code> python manage.py runserver</code></pre>
<h2 id="其他-1">其他:</h2>
<p>服务端代码: <a href="https://github.com/phodal/django-elasticsearch" class="uri">https://github.com/phodal/django-elasticsearch</a></p>
<p>客户端代码: <a href="https://github.com/phodal/ionic-elasticsearch" class="uri">https://github.com/phodal/ionic-elasticsearch</a></p>
<p>上一篇中我们说到了<a href="http://www.phodal.com/blog/django-elasticsearch-haystack-prepare-enviorment/">Django Haystack ElasticSearch 环境准备</a>,接着实战啦~~</p>
<p>官方有一个简单的文档说明空间搜索—— <a href="http://django-haystack.readthedocs.org/en/latest/spatial.html">Spatial Search</a></p>
<p>里面只有<code>Solr</code>和<code>ElasticSearch</code>是支持的,当然我们也不需要这么复杂的特性。</p>
<p>创建Django app名为nx,目录结构如下</p>
<pre><code>.
|______init__.py
|____api.py
|____models.py
|____search_indexes.py
|____templates
| |____search
| | |____indexes
| | | |____nx
| | | | |____note_text.txt</code></pre>
<p>api.py是后面要用的。</p>
<h3 id="django-haystack-model创建">Django Haystack Model创建</h3>
<p>而一般的model没有什么区别,除了修改了save方法</p>
<pre><code>from django.contrib import admin
from django.contrib.gis.geos import Point
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from django.db import models
from pygeocoder import Geocoder
class Note(models.Model):
title = models.CharField("标题", max_length=30, unique=True)
latitude = models.FloatField(blank=True)
longitude = models.FloatField(blank=True)
def __unicode__(self):
return self.title
def save(self, *args, **kwargs):
results = Geocoder.geocode(self.province + self.city + self.address)
self.latitude = results[0].coordinates[0]
self.longitude = results[0].coordinates[1]
super(Note, self).save(*args, **kwargs)
def get_location(self):
return Point(self.longitude, self.latitude)
def get_location_info(self):
return self.province + self.city + self.address
admin.site.register(Note)</code></pre>
<p>通过<code>Geocoder.geocode</code> 解析用户输入的地址,为了方便直接后台管理了。</p>
<h3 id="创建search_index">创建search_index</h3>
<p>在源码的目录下有一个<code>search_indexes.py</code>的文件就是用于索引用的。</p>
<pre><code>from haystack import indexes
from .models import Note
class NoteIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
title = indexes.CharField(model_attr='title')
location = indexes.LocationField(model_attr='get_location')
location_info = indexes.CharField(model_attr='get_location_info')
def get_model(self):
return Note</code></pre>
<p>与些同时我们还需要在<code>templates/search/indexes/nx/</code>目录中有<code>note_text.txt</code>里面的内容是:</p>
<pre><code>{{ object.title }}
{{ object.get_location }}
{{ object.get_location_info }}</code></pre>
<h2 id="创建数据">创建数据</h2>
<p>migrate数据库</p>
<pre><code>python manage.py migrate</code></pre>
<p>run</p>
<pre><code>python manage.py runserver</code></pre>
<p>接着我们就可以后台创建数据了。 打开: http://127.0.0.1:8000/admin/nx/note/,把除了<code>Latitude</code>和<code>Longitude</code>以外的数据都一填——经纬度是自动生成的。就可以创建数据了。</p>
<h3 id="测试">测试</h3>
<p>访问 http://localhost:9200/haystack/_search</p>
<p>或者</p>
<pre><code>curl -XGET http://127.0.0.1:9200/haystack/_search</code></pre>
<h2 id="其他-2">其他:</h2>
<p>服务端代码: <a href="https://github.com/phodal/django-elasticsearch" class="uri">https://github.com/phodal/django-elasticsearch</a></p>
<p>客户端代码: <a href="https://github.com/phodal/ionic-elasticsearch" class="uri">https://github.com/phodal/ionic-elasticsearch</a></p>
<p>在上一篇<a href="http://www.phodal.com/blog/django-elasticsearch-ionic-build-gis-application-create-model/">《GIS 移动应用实战 —— Django Haystack ElasticSearch 构建》</a>中,我们构建了我们的服务端,可以通过搜索搜索到结果,这一篇,我们来构建一个简单的搜索。</p>
<p>最后效果如下图所示:</p>
<figure>
<img src="/static/media/uploads/node.png" alt="Ionic ElasticSearch" /><figcaption>Ionic ElasticSearch</figcaption>
</figure>
<h2 id="开始之前">开始之前</h2>
<p>如果你没有Ionic的经验,可以参考一下之前的一些文章:<a href="http://www.phodal.com/blog/ionic-development-android-ios-windows-phone-application/">《HTML5打造原生应用——Ionic框架简介与Ionic Hello World》</a>。</p>
<p>我们用到的库有:</p>
<ul>
<li>elasticsearch</li>
<li>ionic</li>
<li>ngCordova</li>
</ul>
<p>将他们添加到<code>bower.json</code>,然后 bower install</p>
<p>吧</p>
<h2 id="ionic-elasticsearch-创建页面">Ionic ElasticSearch 创建页面</h2>
<p>1.引入库</p>
<p>在<code>index.html</code>中添加</p>
<pre><code><script src="lib/elasticsearch/elasticsearch.angular.min.js"></script>
<script src="lib/ngCordova/dist/ng-cordova.js"></script></code></pre>
<p>接着开始写我们的搜索模板<code>tab-search.html</code></p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><ion-view</span><span class="ot"> view-title=</span><span class="st">"搜索"</span><span class="ot"> ng-controller=</span><span class="st">"SearchCtrl"</span><span class="kw">></span>
<span class="kw"><ion-content></span>
<span class="kw"><div</span><span class="ot"> id=</span><span class="st">"search-bar"</span><span class="kw">></span>
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"item item-input-inset"</span><span class="kw">></span>
<span class="kw"><label</span><span class="ot"> class=</span><span class="st">"item-input-wrapper"</span><span class="ot"> id=</span><span class="st">"search-input"</span><span class="kw">></span>
<span class="kw"><i</span><span class="ot"> class=</span><span class="st">"icon ion-search placeholder-icon"</span><span class="kw">></i></span>
<span class="kw"><input</span><span class="ot"> type=</span><span class="st">"search"</span><span class="ot"> placeholder=</span><span class="st">"Search"</span><span class="ot"> ng-model=</span><span class="st">"query"</span><span class="ot"> ng-change=</span><span class="st">"search(query)"</span><span class="ot"> autocorrect=</span><span class="st">"off"</span><span class="kw">></span>
<span class="kw"></label></span>
<span class="kw"></div></span>
<span class="kw"></div></span>
<span class="kw"></ion-content></span>
<span class="kw"></ion-view></span></code></pre></div>
<p>显示部分</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><ion-list></span>
<span class="kw"><ion-item</span><span class="ot"> class=</span><span class="st">"item-remove-animate item-icon-right"</span><span class="ot"> ng-repeat=</span><span class="st">"result in results"</span><span class="kw">></span>
<span class="kw"><h2</span><span class="ot"> class=</span><span class="st">"icon-left"</span><span class="kw">></span>{{result.title}}<span class="kw"></h2></span>
<span class="kw"><p></span>简介: {{result.body}}<span class="kw"></p></span>
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"icon-left ion-ios-home location_info"</span><span class="kw">></span>
{{result.location_info}}
<span class="kw"></div></span>
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"button icon-left ion-ios-telephone button-calm button-outline"</span><span class="kw">></span>
<span class="kw"><a</span><span class="ot"> ng-href=</span><span class="st">"tel: {{result.phone_number}}"</span><span class="kw">></span>{{result.phone_number}}<span class="kw"></a></span>
<span class="kw"></div></span>
<span class="kw"></ion-item></span>
<span class="kw"></ion-list></span></code></pre></div>
<p>而我们期待的<code>SearchCtrl</code>则是这样的</p>
<pre><code>$scope.query = "";
var doSearch = ionic.debounce(function(query) {
ESService.search(query, 0).then(function(results){
$scope.results = results;
});
}, 500);
$scope.search = function(query) {
doSearch(query);
}</code></pre>
<p>当我们点下搜索的时候,调用 ESService.</p>
<h2 id="ionic-elasticsearch-service">Ionic ElasticSearch Service</h2>
<p>接着我们就来构建我们的ESService,下面的部分来自网上:</p>
<pre><code>angular.module('starter.services', ['ngCordova', 'elasticsearch'])
.factory('ESService',
['$q', 'esFactory', '$location', '$localstorage', function($q, elasticsearch, $location, $localstorage){
var client = elasticsearch({
host: $location.host() + ":9200"
});
var search = function(term, offset){
var deferred = $q.defer(), query, sort;
if(!term){
query = {
"match_all": {}
};
} else {
query = {
match: { title: term }
}
}
var position = $localstorage.get('position');
if(position){
sort = [{
"_geo_distance": {
"location": position,
"unit": "km"
}
}];
} else {
sort = [];
}
client.search({
"index": 'haystack',
"body": {
"query": query,