刚入新公司不久,公司就有一需求:需要建立一个文档系统的全文检索功能,并单独部署。
虽然我有几年的编程经验,但真的没接触过“全文检索”这个东西,一开始便一头雾水,幸好多得经理指点,并发了相关资料给我阅读消化。经一番折腾,终于弄个明白,并将这个需求消灭掉。
全文检索等相关的概念就不多说了,园里及网络上多的是,想要理论武装就赶紧查!现在就开始讲述下大体的搭建过程。
首先下载lucene.net和PanGu的dll ,盘古分词:http://pangusegment.codeplex.com/
我下载的lucene.net是2.9.2版本,盘古是PanGu_Release_V2.3.1.0。 lucene.net就没什么,只要下载到lucene.net.dll就可以了,而盘古Release会有很多文件,后面会讲到。
现在资料齐全就可以开始创建项目了,建立一个WebService吧。创建后,先引用所需要类库,分别是:Lucene.Net , PanGu , PanGu.HighLight(用于结果返回时的高亮显示) , PanGu.Lucene.Analyzer
然后就可以写代码了。现在我就以创建索引和检索索引为例。
记得先引用类库
using Lucene.Net.Index; using Lucene.Net.Documents; using Lucene.Net.Analysis.Standard; using Lucene.Net.Search; using Lucene.Net.QueryParsers; using Lucene.Net.Analysis; using Lucene.Net.Analysis.PanGu; using System.Text; using PanGu;
创建索引:
1 private static IndexWriter writer = null; 2 private static IndexReader reader = null; 3 4 /// <summary> 5 /// 初始化IndexWriter实例 6 /// </summary> 7 /// <param name="indexDir"></param> 8 private static void InstanceWriter(String indexDir) 9 { 10 writer = new IndexWriter(indexDir, new PanGuAnalyzer()); 11 } 12 13 14 /// <summary> 15 /// 创建索引 16 /// </summary> 17 /// <param name="indexDir">索引文件路径</param> 18 /// <param name="oid">索引ID(自定义)</param> 19 /// <param name="title">标题</param> 20 /// <param name="content">内容</param> 21 /// <param name="optimize">是否进行索引优化</param> 22 /// <returns>创建个数</returns> 23 public static int CreateIndex(string indexDir, string oid, string title, string content, bool optimize) 24 { 25 InstanceWriter(indexDir); 26 27 int num = AddDocument(oid, title, content); 28 29 CloseWriter(optimize); 30 31 return num; 32 }
其中IndexWriter初始化时使用了盘古的分词器,用法很简单就这样:writer = new IndexWriter(indexDir, new PanGuAnalyzer());
AddDocument(..)方法是根据业务要求,将所需要值存入索引文件中,代码如下:
/// <summary> /// 在IndexWriter中添加Document /// </summary> /// <param name="fileOid"></param> /// <param name="filename"></param> /// <param name="content"></param> /// <returns></returns> private static int AddDocument(string fileOid, string filename, string content) { try { Document doc = new Document(); doc.Add(new Lucene.Net.Documents.Field("fileoid", fileOid, Lucene.Net.Documents.Field.Store.YES, Lucene.Net.Documents.Field.Index.NOT_ANALYZED)); doc.Add(new Lucene.Net.Documents.Field("filename", filename, Lucene.Net.Documents.Field.Store.NO, Lucene.Net.Documents.Field.Index.ANALYZED_NO_NORMS)); doc.Add(new Lucene.Net.Documents.Field("filecontent", content, Lucene.Net.Documents.Field.Store.NO, Lucene.Net.Documents.Field.Index.ANALYZED_NO_NORMS)); //为避免重复索引,所以先删除fileoid相同的记录,再重新添加. Term term = new Term("fileoid", fileOid.Trim()); writer.DeleteDocuments(term); writer.AddDocument(doc); return 1; } catch { return 0; } }
CloseWriter(bool optimize)方法则是关闭writer,并在关闭前是否进行优化。对于优化这一操作很讲究,最好不要每次创建索引时都优化,如果索引文件很多的时候,会消耗很多资源的,最好根据业务按需要优化,因为我这只是一个WebService接口,所以将优化权交给了业务系统。代码如下:
/// <summary> /// 关闭IndexWriter,并且是否在关闭前执行索引优化 /// </summary> /// <param name="optimize">是否进行索引优化</param> private static void CloseWriter(bool optimize) { if (optimize) writer.Optimize(); writer.Close(); }
索引检索:
/// <summary> /// 检索文档 (注:此方法只返回FileOid和对应的Score) /// </summary> /// <param name="indexDir">索引文件路径</param> /// <param name="inputStr">检索字符</param> /// <param name="pageSize">每页个数</param> /// <param name="pageNo">页数</param> /// <param name="recCount">总数</param> /// <returns></returns> public static List<SearchFile> SearchIndex(String indexDir, String inputStr, int pageSize, int pageNo, out int recCount) { string keywords = inputStr; IndexSearcher search = new IndexSearcher(indexDir); inputStr = GetKeyWordsSplitBySpace(inputStr, new PanGuTokenizer()); MultiFieldQueryParser parser = new MultiFieldQueryParser(new string[] { "filename", "filecontent" }, new PanGuAnalyzer(true)); Query query = parser.Parse(inputStr); Hits hits = search.Search(query); List<SearchFile> result = new List<SearchFile>(); SearchFile sf = null; recCount = hits.Length(); int i = (pageNo - 1) * pageSize; try { while (i < recCount && result.Count < pageSize) { try { sf = new SearchFile(); sf.FileOid = hits.Doc(i).Get("fileoid"); sf.Score = hits.Score(i); result.Add(sf); } catch (Exception e) { } finally { i++; } } } catch { } finally { search.Close(); } return result; }
检索结果以最简单的方式只返回业务系统的Oid和排序用的Score,对了,在代码里还没有用到高亮设置,由于文档系统的部署不简单,文档里面内容的提取的工作是放到另一个部署服务器中(文件服务器),所以高亮显示工作放在那边来完成,代码如下:
至此,代码完成。但你现在运行是运行不了的,还有些手尾要弄。
上面说了从PanGu下载来的文件有些是不可缺少的。在PanGu_Release_V2.3.1.0文件夹下有个Dictionaries文件夹,这个是存放盘古的词库的,将这个文件夹放在项目的根目录下就可以了。
打开在PanGu_Release_V2.3.1.0文件夹下的Release文件夹,里面有很多东西,不过,有说明文件,打开来看吧。其中有个PanGu.xml文件,
这个是配置分词效果的,将这个文件放在项目发布后的Bin下就可以了。虽然在没有这个文件下,项目也可以运行,但盘古之所以出色,是因为有很好的中文分词效果,而这些效果是根据这个文件配置来定的。所以,一定记得这个配置文件!
有一点要注意的是,在索引检索时关键字和返回的高亮摘要也是经过分词处理的,如果高亮显示是在另一项目完成的话,这个项目部的分词效果配置也应和索引服务器中的分词效果配置一致。
现在,该WebService项目就基本完成了,分词词库方面,可以直接使用盘古自带的词库管理工具,就是在Release文件夹下的DictManage.exe,通过这工具,可以根据各行各业来进行中文分词,很方便,很实用,很强大!
好了,这是小弟有生以来第一次发表的博客,先简单开场,望各位大虾笑纳!哈哈...