前言

首先自问自答几个问题,以让各位看官了解写此文的目的

什么是站内搜索?与一般搜索的区别? 很多网站都有搜索功能,很多都是用SQL语句的Like实现的,但是Like无法做到模糊匹配(例如我搜索“.net学习”,如果有“.net的学习”,Like就无法搜索到,这明显不符合需求,但是站内搜索就能做到),另外Like会造成全盘扫描,会对数据库造成很大压力,为什么不用数据库全文检索,跟普通SQL一样,很傻瓜,灵活性不行

为什么不用百度、google的站内搜索? 毕竟是别人的东西,用起来肯定会受制于人(哪天你的网站火了,它看你不爽了,就可能被K),主要还是索引的不够及时,网站新的内容,需要一定时间才能被索引到,并且用户的体验也不太好

最近改造了《动力起航》的站内搜索的功能,它其实已经有站内搜索的功能,但是是用like来实现的,改造此功能是本着在尽可能少的修改网站的源代码的情况下去改造此功能以及此站内搜索功能可以很好的移植到其他项目的原则来编写!

功能简介

站内搜索使用的技术 Log4Net  日志记录

lucene.Net   全文检索开发包,只能检索文本信息

分词(lucene.Net提供StandardAnalyzer一元分词,按照单个字进行分词,一个汉字一个词)

盘古分词   基于词库的分词,可以维护词库

具体详解

首先我们新增的SearchHelper类需要将其做成一个单例,使用单例是因为:有许多地方需要使用使用,但我们同时又希望只有一个对象去操作,具体代码如下:

  1. #region 创建单例
  2. // 定义一个静态变量来保存类的实例
  3. private static SearchHelper uniqueInstance;
  4.  
  5. // 定义一个标识确保线程同步
  6. private static readonly object locker = new object();
  7.  
  8. // 定义私有构造函数,使外界不能创建该类实例
  9. private SearchHelper()
  10. { }
  11.  
  12. /// <summary>
  13. /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
  14. /// </summary>
  15. /// <returns></returns>
  16. public static SearchHelper GetInstance()
  17. {
  18. // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
  19. // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
  20. // lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
  21. lock (locker)
  22. {
  23. // 如果类的实例不存在则创建,否则直接返回
  24. if (uniqueInstance == null)
  25. {
  26. uniqueInstance = new SearchHelper();
  27. }
  28. }
  29.  
  30. return uniqueInstance;
  31. }
  32. #endregion

其次,使用Lucene.Net需要将被搜索的进行索引,然后保存到索引库以便被搜索,我们引入了“生产者,消费者模式”. 生产者就是当我们新增,修改或删除的时候我们就需要将其在索引库进行相应的操作,我们将此操作交给另一个线程去处理,这个线程就是我们的消费者,使用“生产者,消费者模式”是因为:索引库使用前需解锁操作,使用完成之后必须解锁,所以只能有一个对象对索引库进行操作,避免数据混乱,所以要使用生产者,消费者模式

首先我们来看生产者,代码如下:

  1. private Queue<IndexJob> jobs = new Queue<IndexJob>(); //任务队列,保存生产出来的任务和消费者使用,不使用list避免移除时数据混乱问题
  2.  
  3. /// <summary>
  4. /// 任务类,包括任务的Id ,操作的类型
  5. /// </summary>
  6. class IndexJob
  7. {
  8. public int Id { get; set; }
  9. public JobType JobType { get; set; }
  10. }
  11. /// <summary>
  12. /// 枚举,操作类型是增加还是删除
  13. /// </summary>
  14. enum JobType { Add, Remove }
  15. #region 任务添加
  16. public void AddArticle(int artId)
  17. {
  18. IndexJob job = new IndexJob();
  19. job.Id = artId;
  20. job.JobType = JobType.Add;
  21. logger.Debug(artId + "加入任务列表");
  22. jobs.Enqueue(job);//把任务加入商品库
  23. }
  24.  
  25. public void RemoveArticle(int artId)
  26. {
  27. IndexJob job = new IndexJob();
  28. job.JobType = JobType.Remove;
  29. job.Id = artId;
  30. logger.Debug(artId + "加入删除任务列表");
  31. jobs.Enqueue(job);//把任务加入商品库
  32. }
  33. #endregion

下面是消费者,消费者我们单独一个线程来进行任务的处理:

  1. #region 任务索引
  2. /// <summary>
  3. /// 启动消费者线程
  4. /// </summary>
  5. public void CustomerStart()
  6. {
  7.  
  8. log4net.Config.XmlConfigurator.Configure();
  9.  
  10. PanGu.Segment.Init(PanGuPath);
  11.  
  12. Thread threadIndex = new Thread(IndexOn);
  13. threadIndex.IsBackground = true;
  14. threadIndex.Start();
  15. }
  16.  
  17. /// <summary>
  18. /// 索引任务线程
  19. /// </summary>
  20. private void IndexOn()
  21. {
  22. logger.Debug("索引任务线程启动");
  23. while (true)
  24. {
  25. if (jobs.Count <= )
  26. {
  27. Thread.Sleep( * );
  28. continue;
  29. }
  30. //创建索引目录
  31. if (!System.IO.Directory.Exists(IndexDic))
  32. {
  33. System.IO.Directory.CreateDirectory(IndexDic);
  34. }
  35. FSDirectory directory = FSDirectory.Open(new DirectoryInfo(IndexDic), new NativeFSLockFactory());
  36. bool isUpdate = IndexReader.IndexExists(directory);
  37. logger.Debug("索引库存在状态" + isUpdate);
  38. if (isUpdate)
  39. {
  40. //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁
  41. if (IndexWriter.IsLocked(directory))
  42. {
  43. logger.Debug("开始解锁索引库");
  44. IndexWriter.Unlock(directory);
  45. logger.Debug("解锁索引库完成");
  46. }
  47. }
  48. IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
  49. ProcessJobs(writer);
  50. writer.Close();
  51. directory.Close();//不要忘了Close,否则索引结果搜不到
  52. logger.Debug("全部索引完毕");
  53. }
  54. }
  55. private void ProcessJobs(IndexWriter writer)
  56. {
  57. while (jobs.Count != )
  58. {
  59. IndexJob job = jobs.Dequeue();
  60. writer.DeleteDocuments(new Term("number", job.Id.ToString()));
  61. //如果“添加文章”任务再添加,
  62. if (job.JobType == JobType.Add)
  63. {
  64. BLL.article bll = new BLL.article();
  65. if (bll == null)//有可能刚添加就被删除了
  66. {
  67. continue;
  68. }
  69. Model.article art = bll.GetArticleModel(job.Id);
  70.  
  71. string channel_id = art.channel_id.ToString();
  72. string title = art.title;
  73. DateTime time = art.add_time;
  74. string content = Utils.DropHTML(art.content.ToString());
  75. string Addtime = art.add_time.ToString("yyyy-MM-dd");
  76.  
  77. Document document = new Document();
  78. //只有对需要全文检索的字段才ANALYZED
  79. document.Add(new Field("number", job.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
  80. document.Add(new Field("title", title, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
  81. document.Add(new Field("channel_id", channel_id, Field.Store.YES, Field.Index.NOT_ANALYZED));
  82. document.Add(new Field("Addtime", Addtime, Field.Store.YES, Field.Index.NOT_ANALYZED));
  83. document.Add(new Field("content", content, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
  84. writer.AddDocument(document);
  85. logger.Debug("索引" + job.Id + "完毕");
  86. }
  87. }
  88. }
  89. #endregion

以上我们就把索引库建立完毕了,接下来就是进行搜索了,搜索操作里面包括对搜索关键词进行分词,其次是搜索内容搜索词高亮显示,下面就是搜索的代码:

  1. #region 从索引搜索结果
  2. /// <summary>
  3. /// 从索引搜索结果
  4. /// </summary>
  5. public List<Model.article> SearchIndex(string Words, int PageSize, int PageIndex, out int _totalcount)
  6. {
  7. _totalcount = ;
  8. Dictionary<string, string> dic = new Dictionary<string, string>();
  9. BooleanQuery bQuery = new BooleanQuery();
  10. string title = string.Empty;
  11. string content = string.Empty;
  12. title = GetKeyWordsSplitBySpace(Words);
  13. QueryParser parse = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "title", new PanGuAnalyzer());
  14. Query query = parse.Parse(title);
  15. parse.SetDefaultOperator(QueryParser.Operator.AND);
  16. bQuery.Add(query, BooleanClause.Occur.SHOULD);
  17. dic.Add("title", Words);
  18.  
  19. content = GetKeyWordsSplitBySpace(Words);
  20. QueryParser parseC = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "content", new PanGuAnalyzer());
  21. Query queryC = parseC.Parse(content);
  22. parseC.SetDefaultOperator(QueryParser.Operator.AND);
  23. bQuery.Add(queryC, BooleanClause.Occur.SHOULD);
  24. dic.Add("content", Words);
  25. if (bQuery != null && bQuery.GetClauses().Length > )
  26. {
  27. return GetSearchResult(bQuery, dic, PageSize, PageIndex, out _totalcount);
  28. }
  29. return null;
  30. }
  31. /// <summary>
  32. /// 获取
  33. /// </summary>
  34. /// <param name="bQuery"></param>
  35. private List<Model.article> GetSearchResult(BooleanQuery bQuery, Dictionary<string, string> dicKeywords, int PageSize, int PageIndex, out int totalCount)
  36. {
  37. List<Model.article> list = new List<Model.article>();
  38. FSDirectory directory = FSDirectory.Open(new DirectoryInfo(IndexDic), new NoLockFactory());
  39. IndexReader reader = IndexReader.Open(directory, true);
  40. IndexSearcher searcher = new IndexSearcher(reader);
  41. TopScoreDocCollector collector = TopScoreDocCollector.create(, true);
  42. Sort sort = new Sort(new SortField("Addtime", SortField.DOC, true));
  43. searcher.Search(bQuery, null, collector);
  44. totalCount = collector.GetTotalHits();//返回总条数
  45. TopDocs docs = searcher.Search(bQuery, (Filter)null, PageSize * PageIndex, sort);
  46. if (docs != null && docs.totalHits > )
  47. {
  48. for (int i = ; i < docs.totalHits; i++)
  49. {
  50. if (i >= (PageIndex - ) * PageSize && i < PageIndex * PageSize)
  51. {
  52. Document doc = searcher.Doc(docs.scoreDocs[i].doc);
  53. Model.article model = new Model.article()
  54. {
  55. id = int.Parse(doc.Get("number").ToString()),
  56. title = doc.Get("title").ToString(),
  57. content = doc.Get("content").ToString(),
  58. add_time = DateTime.Parse(doc.Get("Addtime").ToString()),
  59. channel_id = int.Parse(doc.Get("channel_id").ToString())
  60. };
  61. list.Add(SetHighlighter(dicKeywords, model));
  62. }
  63. }
  64. }
  65. return list;
  66. }
  67. /// <summary>
  68. /// 设置关键字高亮
  69. /// </summary>
  70. /// <param name="dicKeywords">关键字列表</param>
  71. /// <param name="model">返回的数据模型</param>
  72. /// <returns></returns>
  73. private Model.article SetHighlighter(Dictionary<string, string> dicKeywords, Model.article model)
  74. {
  75. SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");
  76. Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new Segment());
  77. highlighter.FragmentSize = ;
  78. string strTitle = string.Empty;
  79. string strContent = string.Empty;
  80. dicKeywords.TryGetValue("title", out strTitle);
  81. dicKeywords.TryGetValue("content", out strContent);
  82. if (!string.IsNullOrEmpty(strTitle))
  83. {
  84. string title = model.title;
  85. model.title = highlighter.GetBestFragment(strTitle, model.title);
  86. if (string.IsNullOrEmpty(model.title))
  87. {
  88. model.title = title;
  89. }
  90. }
  91. if (!string.IsNullOrEmpty(strContent))
  92. {
  93. string content = model.content;
  94. model.content = highlighter.GetBestFragment(strContent, model.content);
  95. if (string.IsNullOrEmpty(model.content))
  96. {
  97. model.content = content;
  98. }
  99. }
  100. return model;
  101. }
  102. /// <summary>
  103. /// 处理关键字为索引格式
  104. /// </summary>
  105. /// <param name="keywords"></param>
  106. /// <returns></returns>
  107. private string GetKeyWordsSplitBySpace(string keywords)
  108. {
  109. PanGuTokenizer ktTokenizer = new PanGuTokenizer();
  110. StringBuilder result = new StringBuilder();
  111. ICollection<WordInfo> words = ktTokenizer.SegmentToWordInfos(keywords);
  112. foreach (WordInfo word in words)
  113. {
  114. if (word == null)
  115. {
  116. continue;
  117. }
  118. result.AppendFormat("{0}^{1}.0 ", word.Word, (int)Math.Pow(, word.Rank));
  119. }
  120. return result.ToString().Trim();
  121. }
  122. #endregion

以上我们的站内搜索的SearchHelper类就建立好了,下面来讲讲如何使用,此类提供以下几个方法对外使用:

在Global里面启动消费者线程:

  1. protected void Application_Start(object sender, EventArgs e)
  2. {
  3.  
  4. //启动索引库的扫描线程(生产者)
  5. SearchHelper.GetInstance().CustomerStart();
  6. }

在需被搜索的新增或修改处添加下面方法:

  1. SearchHelper.GetInstance().AddArticle(model.id);

在需被搜索的删除处添加下面方法:

  1. SearchHelper.GetInstance().RemoveArticle(model.id);

搜索的时候使用下面的方法即可:

  1. public List<Model.article> SearchIndex(string Words, int PageSize, int PageIndex, out int _totalcount)

结束语

以上就是整个站内搜索的全部代码,SearchHelper帮助类下载地址:http://files.cnblogs.com/beimeng/SearchHelper.rar

本来想直接提供改造了《动力起航》的源代码,这样就可以直接看到效果了,一方面由于文件过大,另一方面不知道是不是会侵权,所有没有提供下载.如果有需要的朋友可以留下邮箱我将发给你,但仅供学习交流之用,误用做商业用途,以上如果有侵权等问题还请及时告知我,以便我及时更正!

文章来源:http://www.cnblogs.com/beimeng/p/3258967.html

完整的站内搜索Demo(Lucene.Net+盘古分词)的更多相关文章

  1. 完整的站内搜索实战应用(Lucene.Net+盘古分词)

    首先自问自答几个问题,以让各位看官了解写此文的目的 什么是站内搜索?与一般搜索的区别? 多网站都有搜索功能,很多都是用SQL语句的Like实现的,但是Like无法做到模糊匹配(例如我搜索". ...

  2. 使用Lucene.NET实现简单的站内搜索

    使用Lucene.NET实现简单的站内搜索 导入Lucene.NET 开发包 Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和 ...

  3. 一步步开发自己的博客 .NET版(5、Lucenne.Net 和 必应站内搜索)

    前言 这次开发的博客主要功能或特点:    第一:可以兼容各终端,特别是手机端.    第二:到时会用到大量html5,炫啊.    第三:导入博客园的精华文章,并做分类.(不要封我)    第四:做 ...

  4. Lucene.net站内搜索—4、搜索引擎第一版技术储备(简单介绍Log4Net、生产者消费者模式)

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  5. Lucene.net站内搜索—2、Lucene.Net简介和分词

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  6. Lucene.net站内搜索—6、站内搜索第二版

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  7. Lucene.net站内搜索—5、搜索引擎第一版实现

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  8. Lucene.net站内搜索—3、最简单搜索引擎代码

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  9. Lucene.net站内搜索—1、SEO优化

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

随机推荐

  1. 64位 CentOS NDK 编译 FFMPEG

    64位 CentOS NDK 编译 FFMPEG 一.           参考文章: http://www.cnblogs.com/baopu/p/4733029.html http://www.c ...

  2. IOS DLNA开发(CyberLink和PlatinumKit)

    1.CyberLink 和 PlatinumKit 两者的比较 CyberLink大概在2010年之后功能就没有更新,部分功能不够完善,网上有下载地址 http://www.pudn.com/down ...

  3. 【POJ2752】【KMP】Seek the Name, Seek the Fame

    Description The little cat is so famous, that many couples tramp over hill and dale to Byteland, and ...

  4. 微信公众平台开发(免费云BAE+高效优雅的Python+网站开放的API)

    虽然校园App是个我认为的绝对的好主意,但最近有个也不错的营销+开发的模式出现:微信平台+固定域名服务器. 微信公众平台的运行模式不外两个: 一.机器人模式或称转发模式,将说话内容转发到服务器上完成, ...

  5. javaScript 的option触发事件

    先说jquery的option触发事件,很方便 $("option:selected")//这样就能直接触发选择的option了 在JavaScript中就显得比较麻烦,其实< ...

  6. flask request

    请求对象要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:searchword = request.args.get('key', '')用户可能会改变 URL 导致 ...

  7. Android中AppWidget的分析与应用:AppWidgetProvider .

    from: http://blog.csdn.net/thl789/article/details/7887968 本文从开发AppWidgetProvider角度出发,看一个AppWidgetPrv ...

  8. Python核心编程2第一章课后练习

    1-1 在windows下的安装方法在网上下载python2.7直接安装到C盘1)在系统变量中找到path. 2)编辑path值,添加你安装的python路径,C:\Python27. 3)检验pyt ...

  9. iOS · 安装RVM cocoaPods 及问题解决

    一.安装RVM 1.RVM:ruby版本管理器,命令行工具 管理Ruby 开始安装吧~ 对!!就是这样换成taobao ⬇️ $ gem sources -l $ gem sources --remo ...

  10. __block存储类型

    __block存储类型 你可以指定引入一个变量为可更改的,即读-写的,通过应用__block 存储类型修饰符.局部变量的__block 的存储和 register.auto.static 等存储类型相 ...