前言:
我之前沒有接觸過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