前言:
我之前没有接触过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