
Lucene.net站内搜索—5、搜索引擎第一版实现
发布日期:2021-05-09 03:56:32
浏览次数:12
分类:博客文章
本文共 10300 字,大约阅读时间需要 34 分钟。
目录
- 站内搜索模块:生产者、消费者,多线程。复习多线程,用多线程做一个winform的生产者、消费者的例子,有任务的时候(点按钮给整数)就处理任务,没任务的时候就每次扫描都说“还是没任务,睡会再看”,用Sleep模拟耗时操作,线程中操作UI线程的代码见备注。
- 派一个人来管理索引库,想向索引库中写数据的地方都向这个人来发出请求。
- 由于索引库同时只能有一个IndexWriter进行写,所以有一个消费者线程一直保持对IndexWriter写的状态,有新任务进入的时候对IndexWriter写入。如果IndexWriter一直保持打开状态的话,新添加的文档是不会被搜索到的,因此必须处理完队列中的任务后关闭writer,然后下次while循环扫描的时候判断如果队列汇总没有任务,则sleep5秒钟后再判断,防止不断判断给服务器cpu压力
- IndexManager做成单例。维持一个任务的Queue,Thread thread = new Thread(ScanThread); thread.Start();启动一个线程,在ScanThread方法中不断遍历Queue ,当有新任务加入的时候把新任务加入索引库,当要删除文章的时候也是加入一个jobType == JobType.Delete的内容。UpdateDocument
- 文章的更新和删除。
1、线程访问UI线程:
ParameterizedThreadStart threadStart = (obj) => { txtLog.AppendText(obj + "\n"); }; txtLog.Invoke(threadStart, item);
详细代码如下:
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Threading;using log4net;using System.Configuration;using System.Web.Hosting;using RuPeng.Utils;using RuPengSite.DataTier.DataSetThreadTableAdapters;using System.Text;using Lucene.Net.Store;using Lucene.Net.Index;using System.IO;using Lucene.Net.Analysis.PanGu;using Lucene.Net.Documents;namespace RuPengSite.Search{ public class IndexManager { public readonly static IndexManager Instance = new IndexManager(); private HashSetjobs = new HashSet ();//任务的集合 private bool isStopped;//任务是否停止 private static ILog log = LogManager.GetLogger(typeof(IndexManager)); private IndexManager() { } //启动任务 public void Start() { isStopped = false; Thread thread = new Thread(ScanThread); thread.Start(); } //停止任务 public void Stop() { isStopped = true; } /// /// 扫描线程 /// private void ScanThread() { //如果停止,则不再无限循环 while (!isStopped) { Thread.Sleep(5000);//休息5秒钟,尽可能多的累积任务 if (jobs.Count <= 0) { continue;//如果没任务继续睡 } log.Debug("开始索引预处理"); string indexPath = SearchHelper.GetSearchIndexFullPath(); log.Debug("索引路径是:" + indexPath); FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory()); //判断索引目录是否已经存在 bool isUpdate = IndexReader.IndexExists(directory); log.Debug("索引路径存在状态是" + isUpdate); if (isUpdate) { //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁 if (IndexWriter.IsLocked(directory)) { log.Debug("开始解锁索引路径"); IndexWriter.Unlock(directory); } } IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED); try { ProcessJobItems(directory, writer); } finally { log.Debug("开始关闭reader、writer"); writer.Close(); directory.Close(); log.Debug("完成关闭reader、writer"); } } } ////// 处理队列中的任务 /// /// /// private void ProcessJobItems(FSDirectory directory, IndexWriter writer) { log.Debug("开始处理队列中的"+jobs.Count+"个任务"); foreach (var jobItem in jobs.ToArray())//转换为数组,避免读的时候不能修改的问题 { try { ProcessJobItem(writer, jobItem); jobs.Remove(jobItem);//将处理完成的任务移除 } catch (Exception ex) { log.Error("对任务进行处理失败" + jobItem, ex); } } log.Debug("队列中的任务处理完毕"); } private static void ProcessJobItem(IndexWriter writer, IndexJobItem jobItem) { long threadId = jobItem.ThreadId; JobType jobType = jobItem.ItemType; string url = SearchHelper.GetThreadUrl(threadId); if (jobType == JobType.Delete)//判断任务的类型 { log.Debug("将帖子从索引中移除,threadId=" + threadId); writer.DeleteDocuments(new Term(SearchHelper.URL, url));//删除旧的收录 } else if (jobType == JobType.Add) { writer.DeleteDocuments(new Term(SearchHelper.URL, url));//删除旧的收录 var threads = new rp_threadsTableAdapter().GetDataById(threadId); if (threads.Count <= 0) { log.Debug("id为"+threadId+"的帖子不存在!"); return; } string body = SearchHelper.GetThreadContent(threadId);//帖子内容 string title = threads.Single().Subject;//主题 Document document = new Document(); document.Add(new Field(SearchHelper.URL, url, Field.Store.YES, Field.Index.NOT_ANALYZED)); document.Add(new Field(SearchHelper.TITLE, title, Field.Store.YES, Field.Index.NOT_ANALYZED)); document.Add(new Field(SearchHelper.BODY, body, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS)); writer.AddDocument(document); log.Debug("索引帖子" + threadId+"完成"); } else { throw new Exception("错误的jobType:" + jobType); } } ////// 添加帖子的索引任务 /// /// public void AddThread(long threadId) { IndexJobItem jobItem = new IndexJobItem() { ItemType=JobType.Add,ThreadId=threadId}; jobs.Add(jobItem); } ////// 移除帖子的索引任务 /// /// public void DeleteThread(long threadId) { IndexJobItem jobItem = new IndexJobItem() { ItemType = JobType.Delete, ThreadId = threadId }; jobs.Add(jobItem); } class IndexJobItem { public JobType ItemType { get; set; } public long ThreadId { get; set; } public override bool Equals(object obj) { IndexJobItem item = obj as IndexJobItem; if (item == null) { return false; } return this.ItemType==item.ItemType&&this.ThreadId==item.ThreadId; } public override int GetHashCode() { return ToString().GetHashCode(); } public override string ToString() { return ItemType+":"+ThreadId; } } enum JobType {Delete,Add }//任务类型 }}
多条件查询
我看了下淘宝,淘宝的站内搜索只实现了且条件:
或条件查询可以来看看百度,当然,百度是同时采用了且条件和或条件查询的:
在标题和正文中查找
PhraseQuery queryMsg = new PhraseQuery();foreach (string word in CommonHelper.SplitWords(txtKW.Text)) { queryMsg.Add(new Term("msg", word)); }queryMsg.SetSlop(100);PhraseQuery queryTitle = new PhraseQuery();foreach (string word in CommonHelper.SplitWords(txtKW.Text)) { queryTitle.Add(new Term("title", word)); }queryTitle.SetSlop(100);BooleanQuery query = new BooleanQuery(); query.Add(queryMsg, BooleanClause.Occur.SHOULD);query.Add(queryTitle, BooleanClause.Occur.SHOULD);
BooleanQuery相当于盛放其他查询条件的容器,类似于div。第二个参数:Must为必须有,Must_Not为必须没有,Should为可以有
高亮显示
- 高亮显示,只显示包含关键词的部分。参考盘古分词的文档。
- 从网上、文档找来的代码不用细读每行代码,先把它拿过来运行通过再说。
- 不用每次改代码都重启,在项目的属性页面的Web中选中“启用编辑并继续(Enable Edit and Continue)”
- 把font控制颜色改成通过style控制颜色,原则“不要在正文中控制格式”。不推荐使用font、b、i、br等。
private static String highLight(string keyword,String content) { PanGu.HighLight.SimpleHTMLFormatter formatter = new PanGu.HighLight.SimpleHTMLFormatter("", ""); PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(formatter, new Segment()); highlighter.FragmentSize = 500; string msg = highlighter.GetBestFragment(keyword,content); if (string.IsNullOrEmpty(msg)) { return content; } else { return msg; } } String hightlightTitle = highLight(keyword, title); String hightlightBody = HttpUtility.HtmlEncode(body);//防止XSS攻击 hightlightBody = highLight(keyword, hightlightBody);
路径可配置化
- 连接配置信息放到Web.Config的ConnectionStrings段中,而普通的自定义配置则可以写到AppSettings段中,哪些需要配置:索引的路径,被索引的网站url,索引的时间间隔。
- 读取string indexPath = ConfigurationManager.AppSettings["IndexPath"],使用ConfigurationManager添加引用System.Configuration
- 使用request.MapPath或者Server.MapPath把相对于网站根路径的路径转换为绝对路径(不是转换为http://www.baidu.com/a.aspx,转换为c:/baidu_com/a.aspx)。在定时任务等不在Http线程中取HttpContext.Current得到的是null,因此在定时任务中不能用HttpContext.Current.Server.MapPath方法来转换,要用HostingEnvironment.MapPath,因此可以在其他地方也用HostingEnvironment.MapPath。
- 修改Web.config会造成IIS重启,这样会立即加载新的任务
解决:地址无法发给好友
我们先看下淘宝的站内搜索:
细心看,我们会发现url是一连串的字符串,可以肯定这是采用了get请求的方式。
- 用户没法把搜索结果页面发给好友,要用Get提交,这样才能得到搜索页面地址。如果采用Get方式的话,要删掉form的runat=server,变成HTML的form、method改为get,所有控件都要用HTML控件。因为只有去掉runat=server的form,才会完全去掉ViewState
- 注意input不能只指定id,而应该指定name,否则不会出现在querystring中。Id是供Javascript用的,name是供querystring/Request用的。对于type=submit的input来说,只有被点击的input的name、value才会被提交给服务器。
- method改为get
- 1、要删掉form的runat=server。(唯一去掉viewstate的方法)
- 2、所有除了DataBound控件(比如GridView、Repeater等)都要用HTML控件。Repeater、ObjectDataSource之类控件不需要runat=server的form也可以,但是VS总是提示,去源代码视图拖放、让他生成再手动删掉。
- 3、控件注意要给表单name属性赋值。
- 4、在后台Page_Load代码中进行响应
- 5、IsPostBack不再有用,只能通过判断参数是否为空来判断是否是提交的页面。
- 点搜索按钮以后如何显示搜索关键字:在aspx.cs中定义一个GetKeyWord方法,<input type="text" id="kw1" name="kw" value='<%=Request["kw"] %>'/>
只要有runat=server的form就会产生__VIEWSTATE等,所以去掉form的runat=server,这样除了Repeater等少数控件之外服务端控件都没法使用,只能使用html标签。这是为什么说“要求高的互联网项目不用服务端控件”。面试时候说:我在有的项目中没有用服务端控件的例子。
为了能让查询参数显示在地址栏中,方便传播地址,把form的method改为get;因为ViewState太长,所以影响美观,因此禁用ViewState;但是发现哪怕禁用ViewState,ViewState也没有完全消失;研究发现,只有去掉form的runat=server后才能完全干掉ViewState;但是,一旦去掉form的runat=server后几乎所有的WebForm控件都用不了(除了Repeater等少数几个和input无关的之外),只能用html控件,然后在Page_Load中进行响应。
发表评论
最新留言
感谢大佬
[***.8.128.20]2025年03月30日 02时21分58秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
IO多路复用小故事
2021-05-09
码云 Pages 搭建
2021-05-09
《论可计算数及其在判定上的应用》简单理解
2021-05-09
中国剩余定理证明过程
2021-05-09
java中Object.equals()简单用法
2021-05-09
poj 2187 Beauty Contest(凸包求解多节点的之间的最大距离)
2021-05-09
java中自动装箱的问题
2021-05-09
程序员的开发文档
2021-05-09
mybatis generator修改默认生成的sql模板
2021-05-09
算法 - 如何从股票买卖中,获得最大收益
2021-05-09
算法 - 链表操作思想 && case
2021-05-09
并发编程实战-ConcurrentHashMap源码解析
2021-05-09
C#之反射、元数据详解
2021-05-09
通俗易懂设计模式解析——单例模式
2021-05-09
通俗易懂设计模式解析——抽象工厂模式
2021-05-09
SSM商城项目(十二)
2021-05-09
第5章选择结构程序设计
2021-05-09
前端数据渲染及mustache模板引擎的简单实现
2021-05-09
设计模式系列之工厂模式三兄弟(Factory Pattern)
2021-05-09
OAuth2.0认证详解
2021-05-09