Lucene .NET 全文检索

近期做项目中有用到过Lucene,那个模块是由一位前端大神负责的,空闲时间我也做了个关于Lucene做全文检索的Demo,记录下来,方便以后学习。
关于Lucene的原理,网上有长篇大论的文章,有兴趣的话可以去阅读,再次我就直奔主题,在代码中分析其原理。

1、创建索引(此处我用的是盘古分词)

注:在后台代码的第一行上加上 #define notes这样一行代码,目的是可以用外侧代码的#if,作用嘛 用过之后就很明白了,嘿嘿。

        #region 创建索引 void CreateIndex(object sender, EventArgs e)
        /// <summary>
        /// 创建索引
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CreateIndex(object sender, EventArgs e)
        {
            //索引存放的物理路径
            //this.CreateDirectory();   //给 indexPath 赋值
            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());
            bool isUpdate = IndexReader.IndexExists(directory); //判断索引库文件夹存在并且存在索引库特征文件
            if (isUpdate)
            {
                //同时只能有一段代码对索引库进行写操作!当使用IndexWriter打开directory的时候会自动给索引库上锁。!!!
                //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁
                if (IndexWriter.IsLocked(directory))   //如果索引库文件被锁定了  解锁
                {
                    IndexWriter.Unlock(directory);
                }
            }
            //IndexWriter writer = new IndexWriter(indexPath, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);  //该方法已过时。
            IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
            IEnumerable<Story> list = bllHelper.GetAllStory();
            foreach (Story story in list)
            {
                writer.DeleteDocuments(new Term("ID", story.ID.ToString()));
                Document document = new Document();  //一篇文章,一部小说
                //要进行全文检索的字段要设置 Field.Index.ANALYZED !!!!!!!!!!!!!!!!!!!!!!!!!!
                document.Add(new Field("ID", story.ID.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
                document.Add(new Field("Title", story.Title, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
                document.Add(new Field("Author", story.Author, Field.Store.YES, Field.Index.NOT_ANALYZED));
                document.Add(new Field("Content", story.Content, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
                document.Add(new Field("URL", story.URL, Field.Store.YES, Field.Index.NOT_ANALYZED));
                writer.AddDocument(document);
            }
            writer.Close();
            directory.Close();
        }
        #endregion

2.接下来就是搜索了

        #region 搜索 IEnumerable<Story> Search(string keyWord)
        /// <summary>
        /// 搜索
        /// </summary>
        /// <param name="keyWords">关键字</param>
        private IEnumerable<Story> Search(string keyWord)
        {
            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());
            IndexReader reader = IndexReader.Open(directory, true);
            IndexSearcher searcher = new IndexSearcher(reader);

            //多条件查询
            //搜索条件
            PhraseQuery queryTitle = new PhraseQuery();
            //把用户输入的“北京是首都”分词为“北京 是 首都”三个词,然后添加查询条件
            foreach (string word in CommonHelper.SplitWords(keyWord))
            {
                queryTitle.Add(new Term("Title", word));
            }
            queryTitle.SetSlop(100); //多个查询条件的词之间的最大距离。在文章中相隔太远一般也就无意义

            //搜索条件
            PhraseQuery queryContent = new PhraseQuery();
            //把用户输入的“北京是首都”分词为“北京 是 首都”三个词,然后添加查询条件
            foreach (string word in CommonHelper.SplitWords(keyWord))
            {
                queryContent.Add(new Term("Content", word));
            }
            queryContent.SetSlop(100);

            //用BooleanQuery把多个查询条件拼接起来成为一个大的查询条件
            BooleanQuery query = new BooleanQuery();
            query.Add(queryTitle, BooleanClause.Occur.SHOULD);//可以有
            query.Add(queryContent, BooleanClause.Occur.SHOULD);//可以有

#if !notes

            //组合关系代表的意思如下: 
            //1、MUST和MUST表示“与”的关系,即“并集”。
            //2、MUST和MUST_NOT前者包含后者不包含。
            //3、MUST_NOT和MUST_NOT没意义
            //4、SHOULD与MUST表示MUST,SHOULD失去意义; 
            //5、SHOUlD与MUST_NOT相当于MUST与MUST_NOT。 
            //6、SHOULD与SHOULD表示“或”的概念。  
#endif
            //create 一个存储查询结果的容器
            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);
            searcher.Search(query, null, collector);
            ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;  //得到所有查询结果中的文档

            List<Story> list = new List<Story>();
            foreach (ScoreDoc doc in docs)
            {
                int docID = doc.doc;  //得到查询结果文档的id(Lucene内部分配的id)
                Document document = searcher.Doc(docID);  //根据ID找到对应的Document
                Story story = new Story();
                story.ID = Convert.ToInt32(document.Get("ID"));
                story.Title = CommonHelper.Highlight(keyWord, document.Get("Title"));
                story.Author = document.Get("Author");
                story.Content = CommonHelper.Highlight(keyWord, document.Get("Content"));
                //story.Content = document.Get("Content");
                story.URL = document.Get("URL");
                list.Add(story);
            }
            return list;
        }
        #endregion

3.帮助类文件

3.1 BusinessHelper类

 #region 根据ID获取小说 +Story GetStoryById(int id)
        /// <summary>
        /// 根据ID获取小说
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns></returns>
        public Story GetStoryById(int id)
        {
            string sql = "SELECT * FROM Story nolock WHERE Id = @Id";
            using (SqlDataReader reader = SqlHelper.ExecuteDataReader(sql, new SqlParameter("@Id", id)))
            {
                if (reader.Read())
                {
                    return ToModel(reader);
                }
                else
                {
                    return null;
                }
            }
        } 
        #endregion

        #region 获取所有的小说 +IEnumerable<Story> GetAllStory()
        /// <summary>
        /// 获取所有的小说
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Story> GetAllStory()
        {
            var list = new List<Story>();
            string sql = "SELECT * FROM Story nolock";
            using (SqlDataReader reader = SqlHelper.ExecuteDataReader(sql))
            {
                while (reader.Read())
                {
                    list.Add(ToModel(reader));
                }
            }
            return list;
        } 
        #endregion

        #region 把SqlDataReader转换成实体 Story ToModel(SqlDataReader reader)
        /// <summary>
        /// 把SqlDataReader转换成实体
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private Story ToModel(SqlDataReader reader)
        {
            Story story = new Story();
            story.ID = (int)ToModelValue(reader, "Id");
            story.Title = (string)ToModelValue(reader, "Title");
            story.Author = (string)ToModelValue(reader, "Author");
            story.Content = (string)ToModelValue(reader, "Content");
            story.URL = (string)ToModelValue(reader, "URL");
            return story;
        } 
        #endregion

        private object ToDBValue(object value)
        {
            if (value == null)
            {
                return DBNull.Value;
            }
            else
            {
                return value;
            }
        }

        private object ToModelValue(SqlDataReader reader, string columnName)
        {
            if (reader.IsDBNull(reader.GetOrdinal(columnName)))
            {
                return null;
            }
            else
            {
                return reader[columnName];
            }
        }

3.2 CommonHelper类

        /// <summary>
        /// 把用户传入的字符串s分割成一个个的词
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string[] SplitWords(string s)
        {
            List<string> list = new List<string>();

            Analyzer analyzer = new PanGuAnalyzer();

            TokenStream tokenStream = analyzer.TokenStream("", new StringReader(s));
            Lucene.Net.Analysis.Token token = null;
            while ((token = tokenStream.Next()) != null)  //Next继续分词,如果没有更多词,则返回null
            {
                list.Add(token.TermText());//得到分到的词
            }
            return list.ToArray();
        }


        public static string Highlight(string keyword, string content)
        {
            try
            {
                //创建HTMLFormatter,参数为高亮单词的前后缀
                PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter =
                       new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\"><b>", "</b></font>");
                //创建 Highlighter ,输入HTMLFormatter 和 盘古分词对象Semgent
                PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new Segment());
                //设置每个摘要段的字符数
                highlighter.FragmentSize = 5000;
                //获取最匹配的摘要段
                string result = highlighter.GetBestFragment(keyword, content);
                if (string.IsNullOrEmpty(result))
                {
                    return content;
                }
                else
                {
                    return result;
                }
            }
            catch
            {
                return content;
            }
        }

3.3 SqlHelper 类

        public static string CONNECTIONSTRING = ConfigurationManager.ConnectionStrings["connLuceneDB"].ConnectionString;

        #region 执行查询方法 +static DataTable ExecuteDataTable(string sql)
        /// <summary>
        /// 执行查询方法
        /// <para>返回DataTable</para>
        /// </summary>
        /// <param name="sql">sql语句</param>
        /// <param name="list"></param>
        public static DataTable ExecuteDataTable(string sql)
        {
            using (SqlConnection conn = new SqlConnection(SqlHelper.CONNECTIONSTRING))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand(sql, conn))
                {
                    SqlDataAdapter da = new SqlDataAdapter(cmd);
                    DataTable dt = new DataTable();
                    da.Fill(dt);
                    return dt;
                }
            };
        } 
        #endregion

        #region 执行查询方法,返回DataReader对象 +static SqlDataReader ExecuteDataReader(string cmdText,params SqlParameter[] parameters)
        /// <summary>
        /// 执行查询方法,返回DataReader对象
        /// </summary>
        /// <param name="cmdText"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static SqlDataReader ExecuteDataReader(string cmdText,
            params SqlParameter[] parameters)
        {
            SqlConnection conn = new SqlConnection(CONNECTIONSTRING);
            conn.Open();
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.CommandText = cmdText;
                cmd.Parameters.AddRange(parameters);
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
        } 
        #endregion

        #region 执行 增、删、改 的方法 +static void ExecuteNonQuery(string sql, out bool flag)
        /// <summary>
        /// 执行 增、删、改 的方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <returns>返回执行结果  true OR false</returns>
        public static bool ExecuteNonQuery(string sql)
        {
            var flag = false;
            using (SqlConnection conn = new SqlConnection(SqlHelper.CONNECTIONSTRING))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand(sql, conn))
                {
                    flag = cmd.ExecuteNonQuery() > 0 ? true : false;
                }
            };
            return flag;
        } 
        #endregion

4.小说实体类

    /// <summary>
    /// 小说 实体类
    /// </summary>
    public class Story
    {
        /// <summary>
        /// 小说编号
        /// </summary>
        public int ID { get; set; }
        /// <summary>
        /// 小说标题
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 作者
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// 小说内容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 小说在线阅读地址
        /// </summary>
        public string URL { get; set; }
    }

5.前台

<form id="form1" runat="server" method="post">
    <asp:TextBox ID="txtKW" runat="server" Width="291px"></asp:TextBox>
    <asp:Button ID="btnSearch" runat="server" Text="搜索" onclick="btnSearch_Click" />
                    
    <asp:Button ID="btnCreateIndex" runat="server" Text="创建索引"
        onclick="btnCreateIndex_Click"/>
    <asp:GridView ID="gdvShowStory" runat="server" AutoGenerateColumns="False" CellPadding="4"
        ForeColor="#333333" GridLines="None">
        <AlternatingRowStyle BackColor="White" ForeColor="#284775" />
        <Columns>
            <asp:TemplateField HeaderStyle-Width="3%">
                <HeaderTemplate>
                    编号
                </HeaderTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text='<%# Eval("ID") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderStyle-Width="10%">
                <HeaderTemplate>
                    标题
                </HeaderTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label2" Text='<%# Eval("Title") %>' runat="server"></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderStyle-Width="8%">
                <HeaderTemplate>
                    作者
                </HeaderTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label2" Text='<%# Eval("Author") %>' runat="server"></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderStyle-Width="70%">
                <HeaderTemplate>
                    内容
                </HeaderTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label2" Text='<%# Eval("Content") %>' runat="server"></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderStyle-Width="5%">
                <HeaderTemplate>
                    操作
                </HeaderTemplate>
                <ItemTemplate>
                    <a href='<%#Eval("URL") %>'>在线阅读</a>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
        <EditRowStyle BackColor="#999999" />
        <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
        <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
        <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
        <RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
        <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
        <SortedAscendingCellStyle BackColor="#E9E7E2" />
        <SortedAscendingHeaderStyle BackColor="#506C8C" />
        <SortedDescendingCellStyle BackColor="#FFFDF8" />
        <SortedDescendingHeaderStyle BackColor="#6F8DAE" />
    </asp:GridView>
    </form>

注:需要引入几个类库

 

 

 

 

 

OK,到此为止,一个简单的Demo出来了,看看效果吧:

 

 

 

 

 

 

 

 

 

 

 

(PS:欢迎广大朋友参与导论关于Lucene的问题,有兴趣的话,可以加偶的扣扣:1686336218,成功的路上有我也有你。)

Lucene .NET 全文检索,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。