120行代碼實現基于Meteor的即時搜索工具
Introduction
Background
我所在的小組的研究領域是軟件開發數據挖掘,在各種的軟件開發數據中,最基礎且最重要的數據是軟件項目的代碼庫,其中包含了項目的所有代碼文件、代 碼的所有版本、代碼提交者的想關信息。我們小組從互聯網的各大開源社區、代碼托管網站爬取了超過20萬個軟件項目的代碼庫,其中既包含 Apache 、 Mozilla 這種大型社區中的項目,也包含托管于 Github 、 Bitbucket 等網站上的大大小小的項目。在此基礎上,小組的師兄們對這些項目的元信息進行了抽取,并將其存儲于 Mongodb 中,對于一個項目我們存儲了項目的:
- 名稱 (prj)
- 所托管的網站 (repo)
- 代碼庫所在位置 (src_loc)
- 提交日志所在位置 (log_loc)
- 提交人數 (n_peo)
- 提交的版本數 (n_cmt)
- 所使用的版本控制系統 (vcs)
- 項目的起止時間 (btime/etime)
- 時間跨度等信息 (span)
以 Hadoop 為例, 它在 Mongodb中的一條記錄為:
{ "_id" : "1430277742.791925", "prj" : "hadoop-common.git", "repo" : "git.apache.org", "src_loc" : "/path/to/datastore/git/git.apache.org_hadoop-common.git", "log_loc" : "/path/to/datastore/git/git.apache.org/hadoop-common.git", "n_peo" : 36, "n_cmt" : 5825, "vcs" : "git" "b_time" : 200601, "e_time" : 201005, "span" : 52, "script" : "", }
然而盡管我們有了大量數據,也提取了這些數據的元信息,但是這些數據一直靜靜的躺在服務器的磁盤上,并沒有被很好利用。其中一個重要的原因是我們缺少一個方便好用的搜索工具對數據進行探索。
Instant Search
而所謂即時搜索(Instant search),就是在使用者鍵入關鍵字的同時即時返回搜索結果,最為常見的例子自然是 Google 和百度了。
Goal
今天我將要完成的目標是基于實驗室存儲于 Mongodb 的代碼項目元數據,實現一個能夠幫小組成員搜索數據的工具,它能夠支持:
- 根據用戶輸入進行即時搜索
- 支持關鍵字的正則匹配
- 支持條件搜索
Framework

引自官網:
Meteor is a complete open source platform for building web and mobile apps in pure JavaScript.
引自36Kr:
Meteor 是一個新鮮出爐的現代網站開發平臺,基礎構架是 Node.JS + MongoDB,它把這個基礎構架同時延伸到了瀏覽器端,如果 App 用純 JavaScript 寫成,JS APIs 和 DB APIs 就可以同時在服務器端和客戶端無差異地調用,本地和遠程數據通過 DDP(Distributed Data Protocol)協議傳輸。
接下來我們開始基于 Meteor 實現上文介紹的即時搜索工具。
In Action
Back End
方便起見,我們將把客戶端、服務器端的代碼放在同一個 javascript 文件中。我們首先獲取 Mongodb 中存儲元數據的 collection:
Items = new Mongo.Collection("log_info");
值得注意的是,這個對象既可在客戶端代碼中使用,也可在服務器端代碼使用, Meteor 會基于 DDP 協議幫我們搞定數據在服務器、客戶端之間的傳輸問題。
在服務器端,我們根據用戶提交的關鍵字對數據庫進行查詢,并發布 (publish) 與之相關的數據:
if (Meteor.isServer) { Meteor.publish('items', function (queryString) { return query(Items, queryString) // To implement later }) }
在客戶端,當用戶鍵入查詢時,我們捕捉鍵盤輸入事件,并將查詢字符串存儲于 Session 中,并根據查詢字符串向服務器端訂閱 (subscribe) 新數據:
Template.body.events({ "keyup #search-box":_.throttle(function(event){ Session.set('queryString', event.target.value) Meteor.subscribe('items', event.target.value) }, 50) })
另外我們需要一個幫助函數對所得數據進行展示,這個函數將會在模板中被使用:
Template.body.helpers({ items: function () { return query(Items, Session.get('queryString')) } });
最后我們來實現上面尚未實現的query函數,函數所完成的工作是:
- 根據用戶的輸入構造查詢數據庫的條件
- 對數據庫進行查詢
- 返回查詢結果
function query(collections, queryString){ var limit = 40 var query = queryString.split(' ') var andArray = [] for (var i = query.length - 1; i >= 0; i--) { if (query[i] == '') { continue } var testSpecial = parseSpecial(query[i]) if (testSpecial != null) { andArray.push(testSpecial) } else { var regEx = new RegExp(query[i], 'ig') andArray.push({prj: regEx}) } } if (andArray.length != 0){ return collections.find({$and: andArray}, {limit:limit}) } else { return collections.find({},{limit:limit}) } }
而其中的parseSpecial函數實現了我們想提供的條件搜索功能,若包含特殊操作符,則構造搜索特殊搜索條件,否則使用對項目名稱進行正則匹配:
// should be DRYer function parseSpecial(str){ var relation var result = {} if (str.indexOf('<') != -1){ relation = str.split('<') result[relation[0]] = { $lt: Number(relation[1])} return result } else if (str.indexOf('>') != -1){ relation = str.split('>') result[relation[0]] = { $gt: Number(relation[1])} return result } else if (str.indexOf('=') != -1){ relation = str.split('=') result[relation[0]] = relation[1] return result } else if (str.indexOf(':') != -1){ relation = str.split(':') result[relation[0]] = new RegExp(relation[1], 'ig') return result } return null; }
至此,后端的所有功能已經全部完成。
Front End
在前端中,我們需要一個模板對數據進行展示:
<template name="item"> <dl class="dl-horizontal"> <dt>Project</dt> <dd>{{prj}}</dd> <dt>Repository</dt> <dd>{{repo}}</dd> <dt>Time Span</dt> <dd>{{b_time}} - {{e_time}}</dd> <dt>Version Control</dt> <dd>{{vcs}}</dd> <dt># Commit</dt> <dd>{{n_cmt}}</dd> <dt># People</dt> <dd>{{n_peo}}</dd> <dt>Source Location</dt> <dd>{{src_loc}}</dd> <dt>Log Location</dt> <dd>{{log_loc}}</dd> <hr> </dl> </template>
并且在body中使用這個模板:
<body> <div class="container"> <h1>Source Repo Search</h1> <form class="form-horizontal" onsubmit="return false;"> <div class="form-group"> <input type="text" class="form-control" id="search-box" placeholder="Type project keyword to search"> </div> </form> <div id='help'> <small>Possible options: repo | b_time | e_time | span | vcs | n_cmt | n_peo </small><br> <small>Possible operators: > | = | < | : </small><br> <small>RegEx supported.</small> </div> {{#each items}} {{> item}} {{/each}} </div> </body>
可以看到 Meteor 所使用的模板語言是非常簡單直接的。另外在head中我們需要bootstrap中的樣式:
<head> <title>SRSearch</title> <link rel="stylesheet" href="/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap-theme.min.css"> </head>
這樣所有功能就完成了,所有代碼(包括空行)不到120行,下面是最終效果:
項目完整代碼可見于 Github。
Summary
今天我基于Meteor 框架,只用了120行代碼實現了一個即時搜索工具的前端、后端,并支持正則、條件搜索功能。可以看到用 Meteor 開發實時應用是非常高效的。考慮到這個工具只使用了 Meteor 所提供特性中的冰山一角,Meteor 所能完成的事情著實令人期待。
來自:http://zhengqm.github.io/code/2015/06/26/instant-search-in-meteor/