前言:
我之前沒有接觸過Lucene.Net相關的知識,最近在園子里看到很多大神在分享這塊的內(nèi)容,深受啟發(fā)。秉著“實踐出真知”的精神,再結(jié)合公司項目的實際情況,有了寫一個Demo的想法,算是對自己能力的考驗吧。
功能描述:
1. 前臺網(wǎng)站把新增的索引項對象(標題、內(nèi)容)序列化后,發(fā)送給MQ
2. MQ接收到消息后先持久化,再推送給消息的消費者
3. 消息的消費者(WinServices)接收到消息后,反序列化成索引項對象,調(diào)用SearchEngine類庫的創(chuàng)建索引方法
4. 前臺網(wǎng)站調(diào)用SearchEngine類庫的查詢方法,并傳入用戶輸入的關鍵字,把查詢后匹配的結(jié)果顯示在View上
注:
1. 為了模擬多個用戶同時新增索引項對象,互聯(lián)網(wǎng)本身就是一個多線程的環(huán)境。這里使用了ActiveMQ的隊列模式(另外還有主題模式,主要用于消息廣播的場景),因為其內(nèi)部維護了一個先進先出的隊列,可以保證每次只能有一個消息被接收,所有其它待接收的都需要排隊等待。
2. 這里引入了分布式項目的思想,前臺網(wǎng)站只復制新增索引項和查詢,MQ負責消息的接收和推送,WinServices負責生成索引文件。
3. 因為還只是Demo,所以很多功能還不完善,離真正企業(yè)級應用還有很大的差距,目的只是想練練手,熟悉下相關的知識點。
流程圖:

架構(gòu)圖:

層次圖:

項目結(jié)構(gòu):

LuceneTest.Entity:定義索引項和查詢結(jié)果類的類庫
LuceneTest.MQ:封裝消息隊列(ActiveMQ)發(fā)送和接收功能的類庫
LuceneTest.Web:用于管理索引項和查詢的MVC工程
LuceneTest.WinService.Test:用于WinService測試的WinForm工程
LuceneTest.SearchEngine:封裝Lucene.Net的創(chuàng)建索引和根據(jù)關鍵字查詢的類庫
關鍵代碼片段:
1 /// <summary> 2 /// 創(chuàng)建索引 3 /// </summary> 4 /// <param name="model"></param> 5 public void CreateIndex(IndexSet model) 6 { 7 //打開 索引文檔保存位置 8 var directory = FSDirectory.Open(new DirectoryInfo(this._indexPath), new NativeFSLockFactory()); 9 //IndexReader:對索引庫進行讀取的類10 var isExist = IndexReader.IndexExists(directory);11 12 if (isExist)13 {14 //如果索引目錄被鎖定(比如索引過程中程序異常退出或另一進程在操作索引庫),則解鎖15 if (IndexWriter.IsLocked(directory))16 //手動解鎖17 IndexWriter.Unlock(directory);18 }19 20 //創(chuàng)建向索引庫寫操作對象,IndexWriter(索引目錄,指定使用盤古分詞進行切詞,最大寫入長度限制)21 //補充:使用IndexWriter打開directory時會自動對索引庫文件上鎖22 var writer = new IndexWriter(directory, new PanGuAnalyzer(), !isExist, IndexWriter.MaxFieldLength.UNLIMITED);23 //新建文檔對象,一條記錄對應索引庫中的一個文檔24 var document = new Document();25 26 //向文檔中添加字段27 //所有字段的值都將以字符串類型保存,因為索引庫只存儲字符串類型數(shù)據(jù)28 29 //Field.Store:是否存儲原文:30 //Field.Store.YES:存儲原值(如顯示原內(nèi)容必須為YES),可以用document.Get取出原值31 //Field.Store.NO:不存儲原值32 //Field.Store.COMPRESS:壓縮存儲33 34 //Field.Index:是否創(chuàng)建索引:35 //Field.Index.NOT_ANALYZED:不創(chuàng)建索引 36 //Field.Index.ANALYZED:創(chuàng)建索引(利于檢索)37 38 //WITH_POSITIONS_OFFSETS:指示不僅保存分割后的詞,還保存詞之間的距離39 document.Add(new Field("title", model.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));40 document.Add(new Field("content", model.Content, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));41 42 //文檔寫入索引庫43 writer.AddDocument(document);44 45 //會自動解鎖46 writer.Close();47 //不要忘了Close,否則索引結(jié)果搜不到48 directory.Close();49 }/// <summary> /// 查詢 /// </summary> /// <param name="keyWord"></param> /// <returns></returns> public List<SearchResult> Search(string keyWord) { var searchResultList = new List<SearchResult>(); //打開 索引文檔保存位置 var directory = FSDirectory.Open(new DirectoryInfo(this._indexPath), new NoLockFactory()); //IndexReader:對索引庫進行讀取的類 var reader = IndexReader.Open(directory, true); //關鍵詞分詞 var words = this.SplitWords(keyWord); //搜索條件 var query = new PhraseQuery(); foreach (var item in words) { query.Add(new Term("content", item)); } //指定關鍵詞相隔最大距離 query.SetSlop(100); //TopScoreDocCollector:存放查詢結(jié)果的容器 var collector = TopScoreDocCollector.create(1000, true); //IndexReader:對索引庫進行查詢的類 var searcher = new IndexSearcher(reader); //根據(jù)query查詢條件進行查詢,查詢結(jié)果放入collector容器 searcher.Search(query, null, collector); //TopDocs:指定0到GetTotalHits(),即所有查詢結(jié)果中的文檔,如果TopDocs(20,10)則意味著獲取第20-30之間文檔內(nèi)容,達到分頁的效果 var docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs; foreach (var item in docs) { var searchResult = new SearchResult(); //得到查詢結(jié)果文檔的id(Lucene內(nèi)部分配的id) var docId = item.doc; //根據(jù)文檔id來獲得文檔對象Document var doc = searcher.Doc(docId); searchResult.Id = docId; searchResult.Title = doc.Get("title"); //高亮顯示 searchResult.Content = this.HightLight(keyWord, doc.Get("content")); searchResultList.Add(searchResult); } return searchResultList; }
查詢頁面View
@{ ViewBag.Title = "Search";}<h2>Search List</h2>@using (Html.BeginForm("Search", "IndexMgr")){ <div> 關鍵字: </div> <div> @Html.TextBox("keyWord") </div> <input type="submit" value="保存" />}@{ var list = this.ViewBag.SearchResultList; if (list != null) { foreach (var item in list) { @Html.Raw("標題:" + item.Title) <br /> @Html.Raw("內(nèi)容:" + item.Content) <hr /> } }}注意事項:
1. 如果使用盤古分詞算法,以下文件的“復制到輸出目錄”需要選擇“如果較新則復制”

2. 本Demo的索引文件保存在WinServices的可執(zhí)行目錄(bin/Debug/IndexData)下面,所以前臺網(wǎng)站要查詢,需要配置索引文件的路徑。
運行效果圖:
1. 新增索引項

2. 查詢

參考文獻:
http://m.survivalescaperooms.com/jiekzou/p/4364780.html
http://m.survivalescaperooms.com/piziyimao/archive/2013/01/31/2887072.html
新聞熱點
疑難解答