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

什么是站内搜索?与一般搜索的区别?

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

本文有借鉴其他大神及园友的技术,在此谢谢!

站内搜索使用的技术

  • Log4Net  日志记录
  • lucene.Net   全文检索开发包,只能检索文本信息
  • 分词(lucene.Net提供StandardAnalyzer一元分词,按照单个字进行分词,一个汉字一个词)
  • 盘古分词   基于词库的分词,可以维护词库

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

#region 创建单例
// 定义一个静态变量来保存类的实例
private static SearchHelper uniqueInstance; // 定义一个标识确保线程同步
private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例
private SearchHelper()
{ } /// <summary>
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
/// </summary>
/// <returns></returns>
public static SearchHelper GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new SearchHelper();
}
} return uniqueInstance;
}
#endregion

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

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

  private Queue<IndexJob> jobs = new Queue<IndexJob>();  //任务队列,保存生产出来的任务和消费者使用,不使用list避免移除时数据混乱问题

        /// <summary>
/// 任务类,包括任务的Id,操作的类型
/// </summary>
class IndexJob
{
public string Id { get; set; }
public JobType JobType { get; set; }
public HaiMi.Data.Article art { get; set; }//这里为项目中的文章实体类,根据需要自己定义
}
/// <summary>
/// 枚举,操作类型是增加还是删除
/// </summary>
enum JobType { Add, Remove }
#region 任务添加
public void AddArticle(HaiMi.Data.Article model)
{
IndexJob job = new IndexJob();
job.Id = model.Id;
job.JobType = JobType.Add;
job.art = model;
logger.Debug(model.Id + "加入任务列表");
jobs.Enqueue(job);//把任务加入商品库 } public void RemoveArticle(HaiMi.Data.Article model)
{
IndexJob job = new IndexJob();
job.JobType = JobType.Remove;
job.art = model;
job.Id = model.Id;
logger.Debug(model.Id + "加入删除任务列表");
jobs.Enqueue(job); } #endregion

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

 #region 任务索引
/// <summary>
/// 索引任务线程
/// </summary>
private void IndexOn()
{
try
{ logger.Debug("索引任务线程启动");
while (true)
{
if (jobs.Count < )
{
Thread.Sleep( * );
continue;
}
//创建索引目录
if (!System.IO.Directory.Exists(IndexDic))
{ System.IO.Directory.CreateDirectory(IndexDic); }
FSDirectory directory = FSDirectory.Open(new DirectoryInfo(IndexDic), new NativeFSLockFactory());
bool isUpdate = IndexReader.IndexExists(directory);
logger.Debug("索引库存在状态" + isUpdate);
if (isUpdate)
{
//如果索引目录被锁定
if (IndexWriter.IsLocked(directory))
{
logger.Debug("开始解锁索引库");
IndexWriter.Unlock(directory);
logger.Debug("解锁索引库完成"); } }
IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
ProcessJobs(writer);
writer.Dispose();
directory.Dispose();
//writer.Close();
//directory.Close();
logger.Debug("全部索引完毕");
}
}
catch (Exception ex)
{
logger.Error(ex.Message,ex);
} } private void ProcessJobs(IndexWriter writer)
{
while (jobs.Count != )
{
IndexJob job = jobs.Dequeue();
writer.DeleteDocuments(new Term("number", job.Id.ToString()));
//如果"添加文章"任务再添加
if (job.JobType == JobType.Add)
{
HaiMi.Data.Article art = new HaiMi.Data.Article();
//这里的数据体通过任务线程传递
art = job.art; if (art == null)
{
continue; } string channel_id = art.Categoryid.ToString();
string title = art.Title;
DateTime time = art.Createtime; string content = HtmlHelper.DropHTML(art.Content.ToString());
string Addtime = art.Createtime.ToString("yyyy-MM-dd"); Document document = new Document();
//只有对需要全文检索的字段才ANALYZED
document.Add(new Field("number", job.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("title", title, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
document.Add(new Field("channel_id", channel_id, Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("Addtime", Addtime, Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("content", content, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
writer.AddDocument(document);
logger.Debug("索引" + job.Id + "完毕"); } } } #endregion
以上我们就把索引库建立完毕了,接下来就是进行搜索了,搜索操作里面包括对搜索关键词进行分词,其次是搜索内容搜索词高亮显示,下面就是搜索的代码:
 #region 从索引搜索结果
public List<Model.article> SearchIndex(string Words, int PageSize, int PageIndex, out int _totalcount)
{ _totalcount = ;
Dictionary<string, string> dic = new Dictionary<string, string>();
BooleanQuery bQuery = new BooleanQuery(); string title = string.Empty;
string content = string.Empty;
title = GetKeyWordsSplitBySpace(Words);
QueryParser parse = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "title", new PanGuAnalyzer());
//Query query = parse.Query(title);
Query query = parse.Parse(title);
parse.DefaultOperator = QueryParser.Operator.AND;
//parse.SetDefaultOperator(QueryParser.Operator.AND);
bQuery.Add(query, Occur.SHOULD);
dic.Add("title", Words);
content = GetKeyWordsSplitBySpace(Words);
QueryParser parseC = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "content", new PanGuAnalyzer());
Query queryC = parseC.Parse(content);
parseC.DefaultOperator = QueryParser.Operator.AND;
bQuery.Add(queryC, Occur.SHOULD);
dic.Add("content", Words);
if (bQuery != null && bQuery.GetClauses().Length > )
{
return GetSearchResult(bQuery, dic, PageSize, PageIndex, out _totalcount);
} return null; }
/// <summary>
/// 获取
/// </summary>
/// <returns></returns>
private List<Model.article> GetSearchResult(BooleanQuery bQuery, Dictionary<string, string> dicKeywords, int PageSize, int PageIndex, out int totalCount)
{
List<Model.article> list = new List<Model.article>();
FSDirectory directory = FSDirectory.Open(new DirectoryInfo(IndexDic), new NoLockFactory());
IndexReader reader = IndexReader.Open(directory, true);
IndexSearcher searcher = new IndexSearcher(reader);
TopScoreDocCollector collector = TopScoreDocCollector.Create(, true);
Sort sort = new Sort(new SortField("Addtime", SortField.DOC, true));
searcher.Search(bQuery, null, collector);
totalCount = collector.TotalHits; //返回总条数
TopDocs docs = searcher.Search(bQuery, (Filter)null, PageSize * PageIndex, sort);
if (docs != null && docs.TotalHits > )
{ for (int i = ; i < docs.TotalHits; i++)
{
if (i >= (PageIndex - ) * PageSize && i < PageIndex * PageSize)
{
Document doc = searcher.Doc(docs.ScoreDocs[i].Doc);
Model.article model = new Model.article()
{
id = doc.Get("number").ToString(),
title = doc.Get("title").ToString(),
content = doc.Get("content").ToString(),
add_time = DateTime.Parse(doc.Get("Addtime").ToString()),
channel_id = doc.Get("channel_id").ToString()
};
list.Add(SetHighlighter(dicKeywords, model));
}
} } return list; }
/// <summary>
/// 设置关键字高亮
/// </summary>
/// <param name="dicKeywords">关键字列表</param>
/// <param name="model">返回的数据模型</param>
/// <returns></returns>
private Model.article SetHighlighter(Dictionary<string, string> dicKeywords, Model.article model)
{
SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");
Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new Segment());
highlighter.FragmentSize = ;
string strTitle = string.Empty;
string strContent = string.Empty;
dicKeywords.TryGetValue("title", out strTitle);
dicKeywords.TryGetValue("content", out strContent);
if (!string.IsNullOrEmpty(strTitle))
{
string title = model.title;
model.title = highlighter.GetBestFragment(strTitle, model.title);
if (string.IsNullOrEmpty(model.title))
{
model.title = title;
}
}
if (!string.IsNullOrEmpty(strContent))
{
string content = model.content;
model.content = highlighter.GetBestFragment(strContent, model.content);
if (string.IsNullOrEmpty(model.content))
{
model.content = content;
}
}
return model;
} /// <summary>
/// 处理关键字为索引格式
/// </summary>
/// <param name="keywords"></param>
/// <returns></returns>
private string GetKeyWordsSplitBySpace(string keywords)
{
PanGuTokenizer ktTokenizer = new PanGuTokenizer();
StringBuilder result = new StringBuilder();
ICollection<WordInfo> words = ktTokenizer.SegmentToWordInfos(keywords);
foreach (WordInfo word in words)
{
if (word == null)
{
continue;
}
result.AppendFormat("{0}^{1}.0 ", word.Word, (int)Math.Pow(, word.Rank));
}
return result.ToString().Trim();
} #endregion

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

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

        /// <summary>
/// 启动消费者线程
/// </summary>
public void CustomerStart()
{
log4net.Config.XmlConfigurator.Configure(); PanGu.Segment.Init(PanGuPath); Thread threadIndex = new Thread(IndexOn);
threadIndex.IsBackground = true;
threadIndex.Start();
}
       protected void Application_Start(object sender, EventArgs e)
{ //启动索引库的扫描线程(生产者)
SearchHelper.GetInstance().CustomerStart();
}

在需被搜索的新增或修改处添加下面方法(model为你项目中的文章实体类):

SearchHelper.GetInstance().AddArticle(model);

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

SearchHelper.GetInstance().RemoveArticle(model);

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

List<Model.article> list = new List<Model.article>();
try
{

int _totalcount = 0;
int PageSize = 10;
int PageIndex = 1;
list= SearchHelper.GetInstance().SearchIndex(words, PageSize, PageIndex, out _totalcount);
}
catch (Exception ex)
{

throw;
}
return View(list);

最后配置一下索引存放目录和分词文件目录

   /// <summary>
/// 索引存放目录
/// </summary>
protected string IndexDic
{
get
{
//return Utils.GetXmlMapPath(DTKeys.FILE_INDEXDICPATH_XML_CONFING);
return System.AppDomain.CurrentDomain.BaseDirectory + "\\lucenedir";
}
} /// <summary>
/// 盘古分词配置目录
/// </summary>
protected string PanGuPath
{
get
{
//return Utils.GetXmlMapPath(DTKeys.FILE_PANGU_XML_CONFING);
return System.AppDomain.CurrentDomain.BaseDirectory + "\\PanGu\\PanGu.xml"; }
}

分词可以从网上下载,然后在PanGu.xml中配置DictionaryPath为你的分词存放路径

<DictionaryPath>..\Dictionaries</DictionaryPath>

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

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

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

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

  2. 完整的站内搜索Demo(Lucene.Net+盘古分词)

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

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

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

  4. Compass实战 站内搜索

    今天早上打算对这两天学习的Lucene以及Compass总结一下,想来想去,还是写个小项目来验证最好了.于是就有了今天的这篇文章.难易程度适合对于Compass或者Lucene刚入门的童鞋,大牛看到后 ...

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. [记录]MySQL读写分离(Atlas和MySQL-proxy)

    MySQL读写分离(Atlas和MySQL-proxy) 一.阿里云使用Atlas从外网访问MySQL(RDS) (同样的方式修改配置文件可以实现代理也可以实现读写分离,具体看使用场景) 1.在跳板机 ...

  2. 去掉chrome浏览器中input获得焦点时的带颜色边框呢

    可以设置表单控件的outline属性为none值, 来去掉Chrome浏览器中输入框以及其它表单控件获得焦点时的带颜色边框. css代码如下: input{outline:none}

  3. 老男孩Python全栈开发(92天全)视频教程 自学笔记07

    day7课程内容: Python的编码解码 二进制 --->ASCII:只能存英文和拉丁字符,一个字符占一个字节,8位 ------->gb2312:只能6700多个中文,1980年 -- ...

  4. Java集合框架(四)—— Queue、LinkedList、PriorityQueue

    Queue接口 Queue用于模拟了队列这种数据结构,队列通常是指“先进先出”(FIFO)的容器.队列的头部保存在队列中时间最长的元素,队列的尾部保存在队列中时间最短的元素.新元素插入(offer)到 ...

  5. 阿里云ECS重置磁盘到SSH登录

    1.登录阿里云(www.aliyun.com) -- > 控制台: 2.点击左边的"云服务器ECS": 3.点击上面"第二步",进入页面之后,点击&quo ...

  6. uva1354 枚举二叉树

    这题很难,这几天一直在想这题,最后看了汝佳大哥的代码才明白.贴上代码 // UVa1354 Mobile Computing // Rujia Liu #include<cstdio> # ...

  7. Centos小白命令

    centos在登录界面无法输入密码 Centos安装flash插件 Centos挂载windows ntfs分区 Centos搭建Eclipse C/C++环境 windows下的txt文件在cent ...

  8. Nginx反向代理实现Tomcat负载均衡

    这篇短文主要介绍Tomcat的集群和用Nginx反向代理实现Tomcat负载均衡. 1.首先需要对一些知识点进行扫盲(对自己进行扫盲,囧): 集群(Cluster) 简单来说就是用N台服务器构成一个松 ...

  9. 跨站请求伪造攻击 CSRF

    摘录: 1.跨站点请求伪造 首先,什么是跨站点请求伪造? 跨站点请求伪造-CSRF(Cross Site Request Forgery):是一种网络攻击方式. 说的白话一点就是,别的站点伪造你的请求 ...

  10. 移动端-手机端-日历选择控件(支持Zepto和JQuery)

    一. 效果图 二. 功能说明 1. 支持切换年份,月份. 2. 支持点击选中日期,也可以点击确定选择日期. 三. 使用方法 1. 添加Input 在你的页面中添加Input输入框.可以再html里,也 ...