使用Lucene.NET实现站内搜索
Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene.Net 是 .NET 版的Lucene。
你可以在这里下载到最新的Lucene.NET
1 using System; 2 using Lucene.Net.Store; 3 using Lucene.Net.Index; 4 using Lucene.Net.Analysis.PanGu; 5 using Lucene.Net.Documents; 6 7 namespace BLL 8 { 9 class IndexHelper 10 { 11 /// <summary> 12 /// 日志小助手 13 /// </summary> 14 static Common.LogHelper logger = new Common.LogHelper(typeof(SearchBLL)); 15 /// <summary> 16 /// 索引保存的位置,保存在配置文件中从配置文件读取 17 /// </summary> 18 static string indexPath = Common.ConfigurationHelper.AppSettingMapPath("IndexPath"); 19 20 /// <summary> 21 /// 创建索引文件或更新索引文件 22 /// </summary> 23 /// <param name="item">索引信息</param> 24 public static void CreateIndex(Model.HelperModel.IndexFileHelper item) 25 { 26 try 27 { 28 //索引存储库 29 FSDirectory directory = FSDirectory.Open(new System.IO.DirectoryInfo(indexPath), new NativeFSLockFactory()); 30 //判断索引是否存在 31 bool isUpdate = IndexReader.IndexExists(directory); 32 if (isUpdate) 33 { 34 //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁 35 if (IndexWriter.IsLocked(directory)) 36 { 37 //解锁索引库 38 IndexWriter.Unlock(directory); 39 } 40 } 41 //创建IndexWriter对象,添加索引 42 IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED); 43 //获取新闻 title部分 44 string title = item.FileTitle; 45 //获取新闻主内容 46 string body = item.FileContent; 47 //为避免重复索引,所以先删除number=i的记录,再重新添加 48 //尤其是更新的话,更是必须要先删除之前的索引 49 writer.DeleteDocuments(new Term("id", item.FileName)); 50 //创建索引文件 Document 51 Document document = new Document(); 52 //只有对需要全文检索的字段才ANALYZED 53 //添加id字段 54 document.Add(new Field("id", item.FileName, Field.Store.YES, Field.Index.NOT_ANALYZED)); 55 //添加title字段 56 document.Add(new Field("title", title, Field.Store.YES, Field.Index.NOT_ANALYZED)); 57 //添加body字段 58 document.Add(new Field("body", body, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS)); 59 //添加url字段 60 document.Add(new Field("url", item.FilePath, Field.Store.YES, Field.Index.NOT_ANALYZED)); 61 //写入索引库 62 writer.AddDocument(document); 63 //关闭资源 64 writer.Close(); 65 //不要忘了Close,否则索引结果搜不到 66 directory.Close(); 67 //记录日志 68 logger.Debug(String.Format("索引{0}创建成功",item.FileName)); 69 } 70 catch (SystemException ex) 71 { 72 //记录错误日志 73 logger.Error(ex); 74 throw; 75 } 76 catch (Exception ex) 77 { 78 //记录错误日志 79 logger.Error(ex); 80 throw; 81 } 82 } 83 84 /// <summary> 85 /// 根据id删除相应索引 86 /// </summary> 87 /// <param name="guid">要删除的索引id</param> 88 public static void DeleteIndex(string guid) 89 { 90 try 91 { 92 ////索引存储库 93 FSDirectory directory = FSDirectory.Open(new System.IO.DirectoryInfo(indexPath), new NativeFSLockFactory()); 94 //判断索引库是否存在索引 95 bool isUpdate = IndexReader.IndexExists(directory); 96 if (isUpdate) 97 { 98 //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁 99 if (IndexWriter.IsLocked(directory)) 100 { 101 IndexWriter.Unlock(directory); 102 } 103 } 104 IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED); 105 //删除索引文件 106 writer.DeleteDocuments(new Term("id", guid)); 107 writer.Close(); 108 directory.Close();//不要忘了Close,否则索引结果搜不到 109 logger.Debug(String.Format("删除索引{0}成功", guid)); 110 } 111 catch (Exception ex) 112 { 113 //记录日志 114 logger.Error(ex); 115 //抛出异常 116 throw; 117 } 118 } 119 } 120 }IndexHelper 添加、更新、删除索引
1 using Lucene.Net.Analysis; 2 using Lucene.Net.Analysis.PanGu; 3 using Lucene.Net.Documents; 4 using Lucene.Net.Index; 5 using Lucene.Net.Search; 6 using Lucene.Net.Store; 7 using Model.HelperModel; 8 using System; 9 using System.Collections.Generic; 10 11 namespace BLL 12 { 13 public static class SearchBLL 14 { 15 //一个类中可能会有多处输出到日志,多处需要记录日志,常将logger做成static 静态变量 16 /// <summary> 17 /// 日志助手 18 /// </summary> 19 static Common.LogHelper logger = new Common.LogHelper(typeof(SearchBLL)); 20 /// <summary> 21 /// 索引保存位置 22 /// </summary> 23 static string indexPath = Common.ConfigurationHelper.AppSettingMapPath("IndexPath"); 24 /// <summary> 25 /// 搜索 26 /// </summary> 27 /// <param name="keywords">用户搜索的关键词</param> 28 /// <returns>返回搜索的结果</returns> 29 public static List<SearchResult> Search(string keywords) 30 { 31 try 32 { 33 //索引存储库 34 FSDirectory directory = FSDirectory.Open(new System.IO.DirectoryInfo(indexPath), new NoLockFactory()); 35 //创建IndexReader对象 36 IndexReader reader = IndexReader.Open(directory, true); 37 //创建IndexSearcher对象 38 IndexSearcher searcher = new IndexSearcher(reader); 39 //新建PhraseQuery 查询对象 40 PhraseQuery query = new PhraseQuery(); 41 //把用户输入的关键词进行拆词 42 foreach (string word in SplitWord(keywords)) 43 { 44 //添加搜索关键词 45 query.Add(new Term("body", word)); 46 } 47 //设置分词间距为100字之内 48 query.SetSlop(100); 49 TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true); 50 //根据查询条件查询结果 51 searcher.Search(query, null, collector); 52 //搜索到的ScoreDoc结果 53 ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs; 54 //保存搜索结果的list 55 List<SearchResult> listResult = new List<SearchResult>(); 56 for (int i = 0; i < docs.Length; i++) 57 { 58 //取到文档的编号(主键,这个是Lucene .net分配的) 59 //检索结果中只有文档的id,如果要取Document,则需要Doc再去取 60 //降低内容占用 61 int docId = docs[i].doc; 62 //根据id找Document 63 Document doc = searcher.Doc(docId); 64 string number = doc.Get("id"); 65 string title = doc.Get("title"); 66 string body = doc.Get("body"); 67 string url = doc.Get("url"); 68 //建立一个搜索结果对象 69 SearchResult result = new SearchResult(); 70 result.Number = number; 71 result.Title = title; 72 result.BodyPreview = Preview(body, keywords); 73 result.Url = url; 74 //添加到结果列表 75 listResult.Add(result); 76 } 77 if (listResult.Count == 0) 78 { 79 return null; 80 } 81 else 82 { 83 return listResult; 84 } 85 } 86 catch (SystemException ex) 87 { 88 logger.Error(ex); 89 return null; 90 } 91 catch (Exception ex) 92 { 93 logger.Error(ex); 94 return null; 95 } 96 } 97 98 /// <summary> 99 /// 获取内容预览 100 /// </summary> 101 /// <param name="body">内容</param> 102 /// <param name="keyword">关键词</param> 103 /// <returns></returns> 104 private static string Preview(string body, string keyword) 105 { 106 //创建HTMLFormatter,参数为高亮单词的前后缀 107 PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>"); 108 //创建 Highlighter ,输入HTMLFormatter 和 盘古分词对象Semgent 109 PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new PanGu.Segment()); 110 //设置每个摘要段的字符数 111 highlighter.FragmentSize = 100; 112 //获取最匹配的摘要段 113 string bodyPreview = highlighter.GetBestFragment(keyword, body); 114 return bodyPreview; 115 } 116 117 /// <summary> 118 /// 盘古分词,对用户输入的搜索关键词进行分词 119 /// </summary> 120 /// <param name="str">用户输入的关键词</param> 121 /// <returns>分词之后的结果组成的数组</returns> 122 private static string[] SplitWord(string str) 123 { 124 List<string> list = new List<string>(); 125 Analyzer analyzer = new PanGuAnalyzer(); 126 TokenStream tokenStream = analyzer.TokenStream("", new System.IO.StringReader(str)); 127 Lucene.Net.Analysis.Token token = null; 128 while ((token = tokenStream.Next()) != null) 129 { 130 list.Add(token.TermText()); 131 } 132 return list.ToArray(); 133 } 134 } 135 }Search 通过查找索引实现搜索
1 namespace Model.HelperModel 2 { 3 public class SearchResult 4 { 5 public string Number { get; set; } 6 7 public string Title { get; set; } 8 9 public string BodyPreview { get; set; } 10 11 public string Url { get; set; } 12 } 13 }SearchResult 模型