Lucene.net站内搜索—5、搜索引擎第一版实现
目錄
Lucene.net站內(nèi)搜索—1、SEO優(yōu)化
Lucene.net站內(nèi)搜索—2、Lucene.Net簡(jiǎn)介和分詞
Lucene.net站內(nèi)搜索—3、最簡(jiǎn)單搜索引擎代碼
Lucene.net站內(nèi)搜索—4、搜索引擎第一版技術(shù)儲(chǔ)備(簡(jiǎn)單介紹Log4Net、生產(chǎn)者消費(fèi)者模式)
Lucene.net站內(nèi)搜索—5、搜索引擎第一版實(shí)現(xiàn)
Lucene.net站內(nèi)搜索—6、站內(nèi)搜索第二版
- ?站內(nèi)搜索模塊:生產(chǎn)者、消費(fèi)者,多線程。復(fù)習(xí)多線程,用多線程做一個(gè)winform的生產(chǎn)者、消費(fèi)者的例子,有任務(wù)的時(shí)候(點(diǎn)按鈕給整數(shù))就處理任務(wù),沒(méi)任務(wù)的時(shí)候就每次掃描都說(shuō)“還是沒(méi)任務(wù),睡會(huì)再看”,用Sleep模擬耗時(shí)操作,線程中操作UI線程的代碼見(jiàn)備注。
- ?派一個(gè)人來(lái)管理索引庫(kù),想向索引庫(kù)中寫(xiě)數(shù)據(jù)的地方都向這個(gè)人來(lái)發(fā)出請(qǐng)求。
- ?由于索引庫(kù)同時(shí)只能有一個(gè)IndexWriter進(jìn)行寫(xiě),所以有一個(gè)消費(fèi)者線程一直保持對(duì)IndexWriter寫(xiě)的狀態(tài),有新任務(wù)進(jìn)入的時(shí)候?qū)ndexWriter寫(xiě)入。如果IndexWriter一直保持打開(kāi)狀態(tài)的話,新添加的文檔是不會(huì)被搜索到的,因此必須處理完隊(duì)列中的任務(wù)后關(guān)閉writer,然后下次while循環(huán)掃描的時(shí)候判斷如果隊(duì)列匯總沒(méi)有任務(wù),則sleep5秒鐘后再判斷,防止不斷判斷給服務(wù)器cpu壓力
- ?IndexManager做成單例。維持一個(gè)任務(wù)的Queue,Thread thread = new Thread(ScanThread); thread.Start();啟動(dòng)一個(gè)線程,在ScanThread方法中不斷遍歷Queue ,當(dāng)有新任務(wù)加入的時(shí)候把新任務(wù)加入索引庫(kù),當(dāng)要?jiǎng)h除文章的時(shí)候也是加入一個(gè)jobType == JobType.Delete的內(nèi)容。UpdateDocument
- 文章的更新和刪除。
1、線程訪問(wèn)UI線程:
ParameterizedThreadStart threadStart = (obj) =>{txtLog.AppendText(obj + "\n");};txtLog.Invoke(threadStart, item);詳細(xì)代碼如下:
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 HashSet<IndexJobItem> jobs = new HashSet<IndexJobItem>();//任務(wù)的集合private bool isStopped;//任務(wù)是否停止private static ILog log = LogManager.GetLogger(typeof(IndexManager));private IndexManager(){ }//啟動(dòng)任務(wù)public void Start(){isStopped = false;Thread thread = new Thread(ScanThread);thread.Start(); }//停止任務(wù)public void Stop(){isStopped = true;}/// <summary>/// 掃描線程/// </summary>private void ScanThread(){//如果停止,則不再無(wú)限循環(huán)while (!isStopped){Thread.Sleep(5000);//休息5秒鐘,盡可能多的累積任務(wù)if (jobs.Count <= 0){continue;//如果沒(méi)任務(wù)繼續(xù)睡 }log.Debug("開(kāi)始索引預(yù)處理");string indexPath = SearchHelper.GetSearchIndexFullPath();log.Debug("索引路徑是:" + indexPath);FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());//判斷索引目錄是否已經(jīng)存在bool isUpdate = IndexReader.IndexExists(directory);log.Debug("索引路徑存在狀態(tài)是" + isUpdate);if (isUpdate){//如果索引目錄被鎖定(比如索引過(guò)程中程序異常退出),則首先解鎖if (IndexWriter.IsLocked(directory)){log.Debug("開(kāi)始解鎖索引路徑");IndexWriter.Unlock(directory);}}IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);try{ProcessJobItems(directory, writer);}finally{log.Debug("開(kāi)始關(guān)閉reader、writer");writer.Close();directory.Close();log.Debug("完成關(guān)閉reader、writer");} }}/// <summary>/// 處理隊(duì)列中的任務(wù)/// </summary>/// <param name="directory"></param>/// <param name="writer"></param>private void ProcessJobItems(FSDirectory directory, IndexWriter writer){log.Debug("開(kāi)始處理隊(duì)列中的"+jobs.Count+"個(gè)任務(wù)");foreach (var jobItem in jobs.ToArray())//轉(zhuǎn)換為數(shù)組,避免讀的時(shí)候不能修改的問(wèn)題 {try{ProcessJobItem(writer, jobItem);jobs.Remove(jobItem);//將處理完成的任務(wù)移除 }catch (Exception ex){log.Error("對(duì)任務(wù)進(jìn)行處理失敗" + jobItem, ex);} }log.Debug("隊(duì)列中的任務(wù)處理完畢");}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)//判斷任務(wù)的類(lèi)型 {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);//帖子內(nèi)容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("錯(cuò)誤的jobType:" + jobType);}}/// <summary>/// 添加帖子的索引任務(wù)/// </summary>/// <param name="threadId"></param>public void AddThread(long threadId){IndexJobItem jobItem = new IndexJobItem() { ItemType=JobType.Add,ThreadId=threadId};jobs.Add(jobItem);}/// <summary>/// 移除帖子的索引任務(wù)/// </summary>/// <param name="threadId"></param>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 }//任務(wù)類(lèi)型 } }
多條件查詢(xún)
我看了下淘寶,淘寶的站內(nèi)搜索只實(shí)現(xiàn)了且條件:
或條件查詢(xún)可以來(lái)看看百度,當(dāng)然,百度是同時(shí)采用了且條件和或條件查詢(xún)的:
在標(biāo)題和正文中查找
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相當(dāng)于盛放其他查詢(xún)條件的容器,類(lèi)似于div。第二個(gè)參數(shù):Must為必須有,Must_Not為必須沒(méi)有,Should為可以有
高亮顯示
- 高亮顯示,只顯示包含關(guān)鍵詞的部分。參考盤(pán)古分詞的文檔。
- 從網(wǎng)上、文檔找來(lái)的代碼不用細(xì)讀每行代碼,先把它拿過(guò)來(lái)運(yùn)行通過(guò)再說(shuō)。
- 不用每次改代碼都重啟,在項(xiàng)目的屬性頁(yè)面的Web中選中“啟用編輯并繼續(xù)(Enable Edit and Continue)”
- 把font控制顏色改成通過(guò)style控制顏色,原則“不要在正文中控制格式”。不推薦使用font、b、i、br等。
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段中,而普通的自定義配置則可以寫(xiě)到AppSettings段中,哪些需要配置:索引的路徑,被索引的網(wǎng)站url,索引的時(shí)間間隔。
- 讀取string indexPath = ConfigurationManager.AppSettings["IndexPath"],使用ConfigurationManager添加引用System.Configuration
- 使用request.MapPath或者Server.MapPath把相對(duì)于網(wǎng)站根路徑的路徑轉(zhuǎn)換為絕對(duì)路徑(不是轉(zhuǎn)換為http://www.baidu.com/a.aspx,轉(zhuǎn)換為c:/baidu_com/a.aspx)。在定時(shí)任務(wù)等不在Http線程中取HttpContext.Current得到的是null,因此在定時(shí)任務(wù)中不能用HttpContext.Current.Server.MapPath方法來(lái)轉(zhuǎn)換,要用HostingEnvironment.MapPath,因此可以在其他地方也用HostingEnvironment.MapPath。
- 修改Web.config會(huì)造成IIS重啟,這樣會(huì)立即加載新的任務(wù)
解決:地址無(wú)法發(fā)給好友
我們先看下淘寶的站內(nèi)搜索:
細(xì)心看,我們會(huì)發(fā)現(xiàn)url是一連串的字符串,可以肯定這是采用了get請(qǐng)求的方式。
- 用戶(hù)沒(méi)法把搜索結(jié)果頁(yè)面發(fā)給好友,要用Get提交,這樣才能得到搜索頁(yè)面地址。如果采用Get方式的話,要?jiǎng)h掉form的runat=server,變成HTML的form、method改為get,所有控件都要用HTML控件。因?yàn)橹挥腥サ魊unat=server的form,才會(huì)完全去掉ViewState
- 注意input不能只指定id,而應(yīng)該指定name,否則不會(huì)出現(xiàn)在querystring中。Id是供Javascript用的,name是供querystring/Request用的。對(duì)于type=submit的input來(lái)說(shuō),只有被點(diǎn)擊的input的name、value才會(huì)被提交給服務(wù)器。
- method改為get
- 1、要?jiǎng)h掉form的runat=server。(唯一去掉viewstate的方法)
- 2、所有除了DataBound控件(比如GridView、Repeater等)都要用HTML控件。Repeater、ObjectDataSource之類(lèi)控件不需要runat=server的form也可以,但是VS總是提示,去源代碼視圖拖放、讓他生成再手動(dòng)刪掉。
- 3、控件注意要給表單name屬性賦值。
- 4、在后臺(tái)Page_Load代碼中進(jìn)行響應(yīng)
- 5、IsPostBack不再有用,只能通過(guò)判斷參數(shù)是否為空來(lái)判斷是否是提交的頁(yè)面。
- 點(diǎn)搜索按鈕以后如何顯示搜索關(guān)鍵字:在aspx.cs中定義一個(gè)GetKeyWord方法,<input type="text" id="kw1" name="kw" value='<%=Request["kw"] %>'/>
只要有runat=server的form就會(huì)產(chǎn)生__VIEWSTATE等,所以去掉form的runat=server,這樣除了Repeater等少數(shù)控件之外服務(wù)端控件都沒(méi)法使用,只能使用html標(biāo)簽。這是為什么說(shuō)“要求高的互聯(lián)網(wǎng)項(xiàng)目不用服務(wù)端控件”。面試時(shí)候說(shuō):我在有的項(xiàng)目中沒(méi)有用服務(wù)端控件的例子。
為了能讓查詢(xún)參數(shù)顯示在地址欄中,方便傳播地址,把form的method改為get;因?yàn)閂iewState太長(zhǎng),所以影響美觀,因此禁用ViewState;但是發(fā)現(xiàn)哪怕禁用ViewState,ViewState也沒(méi)有完全消失;研究發(fā)現(xiàn),只有去掉form的runat=server后才能完全干掉ViewState;但是,一旦去掉form的runat=server后幾乎所有的WebForm控件都用不了(除了Repeater等少數(shù)幾個(gè)和input無(wú)關(guān)的之外),只能用html控件,然后在Page_Load中進(jìn)行響應(yīng)。
轉(zhuǎn)載于:https://www.cnblogs.com/jiekzou/p/4381649.html
總結(jié)
以上是生活随笔為你收集整理的Lucene.net站内搜索—5、搜索引擎第一版实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 树——平衡二叉树插入和查找的JAVA实现
- 下一篇: 机房合作(一):我怎样做组长(敢于承担责