前言:
我之前沒有接觸過Lucene.Net相關的知識,最近在園子裡看到很多大神在分享這塊的內容,深受啟發。秉著“實踐出真知”的精神,再結合公司項目的實際情況,有了寫一個Demo的想法,算是對自己能力的考驗吧。
功能描述:
1. 前台網站把新增的索引項對象(標題、內容)序列化後,發送給MQ
2. MQ接收到消息後先持久化,再推送給消息的消費者
3. 消息的消費者(WinServices)接收到消息後,反序列化成索引項對象,調用SearchEngine類庫的創建索引方法
4. 前台網站調用SearchEngine類庫的查詢方法,並傳入用戶輸入的關鍵字,把查詢後匹配的結果顯示在View上
注:
1. 為了模擬多個用戶同時新增索引項對象,互聯網本身就是一個多線程的環境。這裡使用了ActiveMQ的隊列模式(另外還有主題模式,主要用於消息廣播的場景),因為其內部維護了一個先進先出的隊列,可以保證每次只能有一個消息被接收,所有其它待接收的都需要排隊等待。
2. 這裡引入了分布式項目的思想,前台網站只復制新增索引項和查詢,MQ負責消息的接收和推送,WinServices負責生成索引文件。
3. 因為還只是Demo,所以很多功能還不完善,離真正企業級應用還有很大的差距,目的只是想練練手,熟悉下相關的知識點。
流程圖:
架構圖:
層次圖:
項目結構:
LuceneTest.Entity:定義索引項和查詢結果類的類庫
LuceneTest.MQ:封裝消息隊列(ActiveMQ)發送和接收功能的類庫
LuceneTest.Web:用於管理索引項和查詢的MVC工程
LuceneTest.WinService.Test:用於WinService測試的WinForm工程
LuceneTest.SearchEngine:封裝Lucene.Net的創建索引和根據關鍵字查詢的類庫
關鍵代碼片段:
1 /// <summary> 2 /// 創建索引 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 //創建向索引庫寫操作對象,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 //所有字段的值都將以字符串類型保存,因為索引庫只存儲字符串類型數據 28 29 //Field.Store:是否存儲原文: 30 //Field.Store.YES:存儲原值(如顯示原內容必須為YES),可以用document.Get取出原值 31 //Field.Store.NO:不存儲原值 32 //Field.Store.COMPRESS:壓縮存儲 33 34 //Field.Index:是否創建索引: 35 //Field.Index.NOT_ANALYZED:不創建索引 36 //Field.Index.ANALYZED:創建索引(利於檢索) 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,否則索引結果搜不到 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:存放查詢結果的容器 var collector = TopScoreDocCollector.create(1000, true); //IndexReader:對索引庫進行查詢的類 var searcher = new IndexSearcher(reader); //根據query查詢條件進行查詢,查詢結果放入collector容器 searcher.Search(query, null, collector); //TopDocs:指定0到GetTotalHits(),即所有查詢結果中的文檔,如果TopDocs(20,10)則意味著獲取第20-30之間文檔內容,達到分頁的效果 var docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs; foreach (var item in docs) { var searchResult = new SearchResult(); //得到查詢結果文檔的id(Lucene內部分配的id) var docId = item.doc; //根據文檔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("內容:" + item.Content) <hr /> } } }
注意事項:
1. 如果使用盤古分詞算法,以下文件的“復制到輸出目錄”需要選擇“如果較新則復制”
2. 本Demo的索引文件保存在WinServices的可執行目錄(bin\Debug\IndexData)下面,所以前台網站要查詢,需要配置索引文件的路徑。
運行效果圖:
1. 新增索引項
2. 查詢
參考文獻:
http://www.cnblogs.com/jiekzou/p/4364780.html
http://www.cnblogs.com/piziyimao/archive/2013/01/31/2887072.html