10.5 搜索的優化版

目錄:ASP.NET MVC企業級實戰目錄

像www.verycd.com、博客園、淘寶、京東都有實現站內搜索功能,站內搜索不管在性能和用戶體驗上都很是不錯,本節,經過使用Lucene.Net來實現站內搜索。javascript

演示效果預覽以下圖10-22~10-24所示。css

圖10-22html

 

圖10-23java

 

圖10-24jquery

在10.4節,已經完成了搜索的第一個版本,可是還有許多地方須要優化。好比說,我要統計關鍵詞搜索的頻率高的詞,也即熱詞,以及像百度搜索那樣,在輸入關鍵字後,會自動把搜索相關的熱詞自動如下拉列表的形式帶出來。還有諸如搜索結果分頁,查看文章明細等。redis

10.5.1 熱詞統計

思路:sql

一、  首先,咱們腦海裏要明確一點:搜索關鍵字的統計,實時性是不高的。也就是說咱們能夠按期的去進行統計。數據庫

二、  客戶的每一次搜索記錄,咱們都須要存起來,這樣纔可以統計獲得。安全

從第1點,咱們腦海中就會呈現一張彙總統計表,從第2點中,咱們會想到使用一張搜索記錄明細表。那方案就很明瞭了,只須要按期的從明細表中Group by查詢,而後把查詢結構放到彙總表中。怎麼放到彙總表中?是直接Update更新嗎?其實咱們能夠有更快捷的方式,那就是對彙總表先進行truncate,而後再進行insert操做。服務器

表10-1 搜索彙總統計表SearchTotals

字段名稱

字段類型

說明

Id

char(36)

主鍵,採用Guid方式存儲

KeyWords

nvarchar(50)

搜索關鍵字

SearchCounts

int

搜索次數

表10-2 搜索明細表SearchDetails

字段名稱

字段類型

說明

Id

char(36)

主鍵,採用Guid方式存儲

KeyWords

nvarchar(50)

搜索關鍵字

SearchDateTime

datetime

搜索時間

操做步驟:

(1)在Models文件夾中,新建兩個類SearchTotal、SearchDetail。

SearchTotal.cs代碼:

using System;
using System.ComponentModel.DataAnnotations;

namespace SearchDemo.Models
{
    public class SearchTotal
    {
        public Guid Id { get; set; }
        [StringLength(50)]
        public string KeyWords { get; set; }
        public int SearchCounts { get; set; }
    }
}

SearchDetail.cs代碼:

using System;
using System.ComponentModel.DataAnnotations;

namespace SearchDemo.Models
{
    public class SearchDetail
    {
        public Guid Id { get; set; }
        [StringLength(50)]
        public string KeyWords { get; set; }
        public Nullable<DateTime> SearchDateTime { get; set; }
    }
}

(2)修改SearchDemoContext類,新增了屬性SearchTotal、SearchDetail。

using System.Data.Entity;

namespace SearchDemo.Models
{
    public class SearchDemoContext : DbContext
    {
        public SearchDemoContext() : base("name=SearchDemoContext") { }
        public DbSet<Article> Article { get; set; }
        //下面兩個屬性是新增長的
        public DbSet<SearchTotal> SearchTotal { get; set; }
        public DbSet<SearchDetail> SearchDetail { get; set; }
    }
}

3)更新數據庫

因爲修改了EF上下文,新增了兩個模型類,因此須要進行遷移更新數據庫操做。

將應用程序從新編譯,而後選擇工具->庫程序包管理器->程序包管理控制檯。

打開控制檯,輸入enable-migrations -force ,而後回車。回車後會在項目項目資源管理器中會出現Migrations文件夾,打開Configuration.cs 文件,將AutomaticMigrationsEnabled 值改成 true,而後在控制檯中輸入 update-database 運行。操做完成以後,會在數據庫SearchDemo中多新建兩張表SearchTotals、SearchDetails,而原來的Articles表保持不變。如圖10-20所示。

 

圖10-20

(4)保存搜索記錄

用戶在每次搜索的時候,要把搜索記錄存入SearchDetails表中。爲了方便,這裏我是在用戶每次點擊搜索以後就當即往SearchDetails表中插入記錄了,也就是同步操做,而實際上,若是爲了提高搜索的效率,咱們能夠採用異步操做,即把搜索記錄的數據先寫入redis隊列中,後臺再開闢一個線程來監聽redis隊列,而後把隊列中的搜索記錄數據寫入到數據表中。由於在每次點擊搜索的時候,咱們把記錄往redis寫和把記錄直接往關係型數據庫中寫的效率是相差很大的。

 //先將搜索的詞插入到明細表。
            SearchDetail _SearchDetail = new SearchDetail { Id = Guid.NewGuid(), KeyWords = kw, SearchDateTime = DateTime.Now };
            db.SearchDetail.Add(_SearchDetail);
            int r = db.SaveChanges();

(5)定時更新SearchTotals表記錄

看到這種定時任務操做,這裏能夠採用Quartz.Net框架,爲了方便,我把Quartz.Net的Job寄宿在控制檯程序中,而實際工做中,我則更傾向於將其寄宿在Windows服務中。若是有必要,能夠把這個定時更新SearchTotals表記錄的程序部署到獨立的服務器,這樣能夠減輕Web服務器的壓力。

  1. 新建控制檯程序QuartzNet,添加Quartz.dll和Common.Logging.dll的程序集引用,這裏採用Database First的方式,添加ADO.NET實體數據模型,把表SearchTotals、SearchDetails添加進來。

2.添加KeyWordsTotalService.cs類,裏面封裝兩個方法,清空SearchTotals表,而後把SearchDetails表的分組查詢結構插入到SearchTotals表,這裏我只統計近30天內的搜索明細。

namespace QuartzNet
{
    public class KeyWordsTotalService
    {
        private SearchDemoEntities db = new SearchDemoEntities();
        /// <summary>
        /// 將統計的明細表的數據插入。
        /// </summary>
        /// <returns></returns>
        public bool InsertKeyWordsRank()
        {
            string sql = "insert into SearchTotals(Id,KeyWords,SearchCounts) select newid(),KeyWords,count(*)  from SearchDetails where DateDiff(day,SearchDetails.SearchDateTime,
getdate())<=30 group by SearchDetails.KeyWords
"; return this.db.Database.ExecuteSqlCommand(sql) > 0; } /// <summary> /// 刪除彙總中的數據。 /// </summary> /// <returns></returns> public bool DeleteAllKeyWordsRank() { string sql = "truncate table SearchTotals"; return this.db.Database.ExecuteSqlCommand(sql) > 0; } } }

3. 添加TotalJob.cs類,繼承Ijob接口,並實現Execute方法。

namespace QuartzNet
{
    public class TotalJob : IJob
    {
        /// <summary>
        /// 將明細表中的數據插入到彙總表中。
        /// </summary>
        /// <param name="context"></param>
        public void Execute(JobExecutionContext context)
        {
            KeyWordsTotalService bll = new KeyWordsTotalService();
            bll.DeleteAllKeyWordsRank();
            bll.InsertKeyWordsRank();
        }
    }
}

4.修改Program.cs類

using Quartz;
using Quartz.Impl;
using System;

namespace QuartzNet
{
    class Program
    {
        static void Main(string[] args)
        {
            IScheduler sched;
            ISchedulerFactory sf = new StdSchedulerFactory();
            sched = sf.GetScheduler();
            JobDetail job = new JobDetail("job1", "group1", typeof(TotalJob));//IndexJob爲實現了IJob接口的類
            DateTime ts = TriggerUtils.GetNextGivenSecondDate(null, 5);//5秒後開始第一次運行
            TimeSpan interval = TimeSpan.FromSeconds(50);//每隔50秒執行一次
            Trigger trigger = new SimpleTrigger("trigger1", "group1", "job1", "group1", ts, null,
                                                    SimpleTrigger.RepeatIndefinitely, interval);//每若干時間運行一次,時間間隔能夠放到配置文件中指定

            sched.AddJob(job, true);
            sched.ScheduleJob(trigger);
            sched.Start();
            Console.ReadKey();
        }
    }
}

這裏我是直接把Job和計劃都直接寫到代碼中了,理由仍是由於方便。而實際工做中,咱們應當把這些信息儘可能寫到配置文件中,這樣後面改動起來方便,不須要修改代碼,只須要修改配置文件。

爲了儘快看到效果,我這裏是每隔50秒就進行了一次統計操做,而在實際應用中,咱們的時間間隔多是幾個小時甚至一天,由於像這樣的大數據統計,對實時性的要求不高,咱們能夠儘可能減小對數據庫的IO讀寫次數。

保持運行控制檯程序QuartzNet,而後咱們去進行搜索操做,這樣後臺就按期的生成了搜索統計記錄。

10.5.2 熱門搜索

10.5.2.1 展現熱門搜索

其實就是從表SearchTotals中按照搜索次數進行降序排列,而後取出數條記錄而已。

LastSearch控制器中的Index方法中添加以下代碼:

var keyWords = db.SearchTotal.OrderByDescending(a => a.SearchCounts).Select(x => x.KeyWords).Skip(0).Take(6).ToList();
            ViewBag.KeyWords = keyWords;

View視圖中

<div id="divKeyWords"><span>熱門搜索:</span>@if (ViewBag.KeyWords != null) {
             foreach (string v in ViewBag.KeyWords) { 
              <a href="#">@v</a>
             }
         }</div>

接下來,我想要實現以下圖10-21所示的效果:

 

圖10-21

當我點擊一個熱詞的時候,自動加載到文本框,並點擊「搜索」按鈕。

在View中添加代碼:

<script type="text/javascript">
    $(function () {
        $("#divKeyWords a").click(function () {
            $("#txtSearch").val($(this).html());
            $("#btnSearch").click();
        });
});
</script>
10.5.2.2 搜索下拉框

這裏我引入一個第三方js框架Autocomplete,它能在文本框中輸入文字的時候,自動從後臺抓去數據下拉列表。

雲盤中我提供了Autocomplete.rar,將其解壓,而後拷貝到SearchDemo項目中的lib目錄下。

在SearchDemo項目中的KeyWordsTotalService.cs類中添加方法

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;

namespace SearchDemo.Common
{
    public class KeyWordsTotalService
    {
        private SearchDemoContext db = new SearchDemoContext();

        public List<string> GetSearchMsg(string term)
        {
            try
            {
                //存在SQL注入的安全隱患
                //string sql = "select KeyWords from SearchTotals where KeyWords like '"+term.Trim()+"%'";
                //return db.Database.SqlQuery<string>(sql).ToList();
                string sql = "select KeyWords from SearchTotals where KeyWords like @term";
                return db.Database.SqlQuery<string>(sql, new SqlParameter("@term", term+"%")).ToList();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
    }
}

而後在LastSearch控制器中添加方法:

     /// <summary>
        /// 獲取客戶列表 模糊查詢
        /// </summary>
        /// <param name="term"></param>
        /// <returns></returns>
        public string GetKeyWordsList(string term)
        {
            if (string.IsNullOrWhiteSpace(term))
                return null;

            var list = new KeyWordsTotalService().GetSearchMsg(term);
            //序列化對象
            //儘可能不要用JavaScriptSerializer,爲何?性能差,徹底可用Newtonsoft.Json來代替
            //System.Web.Script.Serialization.JavaScriptSerializer js = new System.Web.Script.Serialization.JavaScriptSerializer();
            //return js.Serialize(list.ToArray());
            return JsonConvert.SerializeObject(list.ToArray());
        }

咱們來看View:

<link href="~/lib/Autocomplete/css/ui-lightness/jquery-ui-1.8.17.custom.css" rel="stylesheet" />
<script src="~/lib/Autocomplete/js/jquery-ui-1.8.17.custom.min.js"></script>
<script type="text/javascript">
    $(function () {
        $("#divKeyWords a").click(function () {
            $("#txtSearch").val($(this).html());
            $("#btnSearch").click();
        });
        getKeyWordsList("txtSearch");
    });
    //自動加載搜索列表
    function getKeyWordsList(txt) {
        if (txt == undefined || txt == "")
            return;
        $("#" + txt).autocomplete({
            source: "/LastSearch/GetKeyWordsList",
            minLength: 1
        });
    }
</script>

10.5.3 標題和內容都支持搜索並高亮展現

在10.4中,只支持在內容中對關鍵詞進行搜索,而實際上,咱們可能既要支持在標題中搜索,也要在內容中搜索。

這裏引入了BooleanQuery,咱們的查詢條件也添加了一個titleQuery。

搜索方法中,以下代碼有修改:

 PhraseQuery query = new PhraseQuery();//查詢條件
            PhraseQuery titleQuery = new PhraseQuery();//標題查詢條件
            List<string> lstkw = LuceneHelper.PanGuSplitWord(kw);//對用戶輸入的搜索條件進行拆分。

            foreach (string word in lstkw)            {
                query.Add(new Term("Content", word));//contains("Content",word)
                titleQuery.Add(new Term("Title", word));
            }
            query.SetSlop(100);//兩個詞的距離大於100(經驗值)就不放入搜索結果,由於距離太遠相關度就不高了

            BooleanQuery bq = new BooleanQuery();
            //Occur.Should 表示 Or , Must 表示 and 運算
            bq.Add(query, BooleanClause.Occur.SHOULD);
            bq.Add(titleQuery, BooleanClause.Occur.SHOULD);

            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);//盛放查詢結果的容器
            searcher.Search(bq, null, collector);//使用query這個查詢條件進行搜索,搜索結果放入collector

10.5.4 與查詢、或查詢、分頁

前面咱們在搜索的時候,其實採用的都是與查詢,也就是說,我輸入「諸葛亮周瑜」,則只會查找出,既存在諸葛亮,又存在周瑜的記錄。那麼有時候,咱們是想查詢存在諸葛亮或者周瑜的記錄的,這也就是所謂的或查詢。

我在界面添加一個複選框「或查詢」,來讓用戶決定採用何種方式進行查詢。

至於分頁,這裏採用MvcPager,關於MvcPager的使用方法請參見4.6.3。

View完整代碼預覽:

@{
    ViewBag.Title = "Index";
}
@model PagedList<SearchDemo.Models.SearchResult>
@using Webdiyer.WebControls.Mvc;
@using SearchDemo.Models;
<style type="text/css">
.search-text2{ display:block; width:528px; height:26px; line-height:26px; float:left; margin:3px 5px; border:1px solid gray; outline:none; font-family:'Microsoft Yahei'; font-size:14px;}
.search-btn2{width:102px; height:32px; line-height:32px; cursor:pointer; border:0px; background-color:#d6000f;font-family:'Microsoft Yahei'; font-size:16px;color:#f3f3f3;}
.search-list-con{width:640px; background-color:#fff; overflow:hidden; margin-top:0px; padding-bottom:15px; padding-top:5px;}
.search-list{width:600px; overflow:hidden; margin:15px 20px 0px 20px;}
.search-list dt{font-family:'Microsoft Yahei'; font-size:16px; line-height:20px; margin-bottom:7px; font-weight:normal;}
.search-list dt a{color:#2981a9;}
.search-list dt a em{ font-style:normal; color:#cc0000;}
#divKeyWords {text-align:left;width:520px;padding-left:4px;}
#divKeyWords a {text-decoration:none;}
#divKeyWords a:hover {color:red;}
</style>
<link href="~/lib/Autocomplete/css/ui-lightness/jquery-ui-1.8.17.custom.css" rel="stylesheet" />
@using(@Html.BeginForm(null, null, FormMethod.Get))
{
    @Html.Hidden("hidfIsOr")
    <div>@Html.TextBox("txtSearch", null, new { @class="search-text2"})<input type="submit" value="搜索" name="btnSearch" id="btnSearch"  class="search-btn2"/><input type="checkbox" id="isOr" value="false"/>或查詢</div>
    <div id="divKeyWords"><span>熱門搜索:</span>@if (ViewBag.KeyWords != null) {
             foreach (string v in ViewBag.KeyWords) { 
              <a href="#">@v</a>
             }
         }</div>
    <div class="search-list-con">
        <dl class="search-list">
            @if (Model != null&& Model.Count > 0)
            {
                foreach (var viewModel in Model)
                {
                <dt><a href="@viewModel.Url" target="_blank">@MvcHtmlString.Create(viewModel.Title)</a><span style="margin-left:50px;">@viewModel.CreateTime</span></dt>
                <dd>@MvcHtmlString.Create(viewModel.Msg)</dd>
                }
            } 
              @Html.Pager(Model, new PagerOptions
 {
     PageIndexParameterName = "id",
     ShowPageIndexBox = true,
     FirstPageText = "首頁",
     PrevPageText = "上一頁",
     NextPageText = "下一頁",
     LastPageText = "末頁",
     PageIndexBoxType = PageIndexBoxType.TextBox,
     PageIndexBoxWrapperFormatString = "請輸入頁數{0}",
     GoButtonText = "轉到"
 })
     <br />
     >>分頁 共有 @(Model==null? 0: Model.TotalItemCount) 篇文章 @(Model==null?0:Model.CurrentPageIndex)/@(Model==null?0:Model.TotalPageCount)
        </dl>
    </div>
    <div>@ViewData["ShowInfo"]</div>
}
<script type="text/javascript">
    $(function () {
        $("#divKeyWords a").click(function () {
            $("#txtSearch").val($(this).html());
            $("#btnSearch").click();
        });
        getKeyWordsList("txtSearch");
        $("#isOr").click(function () {
            if ($(this).attr("checked") == "checked") {
                $("#hidfIsOr").val(true);
            }
            else {
                $("#hidfIsOr").val(false);
            }
        });
        if ($("#hidfIsOr").val() == "true") {
            $("input[type='checkbox']").prop("checked", true);
        }
    });
    //自動加載搜索列表
    function getKeyWordsList(txt) {
        if (txt == undefined || txt == "")
            return;
        $("#" + txt).autocomplete({
            source: "/LastSearch/GetKeyWordsList",
            minLength: 1
        });
    }
</script>
<script src="~/lib/Autocomplete/js/jquery-ui-1.8.17.custom.min.js"></script>
View Code

而後,各位看官請再看LastSearch控制器中的方法:

 public class LastSearchController : Controller
    {
        //
        // GET: /LastSearch/

        string indexPath = System.Configuration.ConfigurationManager.AppSettings["lucenedir"];
        private SearchDemoContext db = new SearchDemoContext();

              public ActionResult Index(string txtSearch, bool? hidfIsOr, int id = 1)
        {
            PagedList<SearchResult> list = null;
            if (!string.IsNullOrEmpty(txtSearch))//若是點擊的是查詢按鈕
            {
                //list = Search(txtSearch);
                list = (hidfIsOr == null || hidfIsOr.Value == false) ? OrSearch(txtSearch, id) : AndSearch(txtSearch, id);
            }
            var keyWords = db.SearchTotal.OrderByDescending(a => a.SearchCounts).Select(x => x.KeyWords).Skip(0).Take(6).ToList();
            ViewBag.KeyWords = keyWords;
            return View(list);
        }
        //與查詢
        PagedList<SearchResult> AndSearch(String kw, int pageNo, int pageLen = 4)
        {
            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());
            IndexReader reader = IndexReader.Open(directory, true);
            IndexSearcher searcher = new IndexSearcher(reader);
            PhraseQuery query = new PhraseQuery();//查詢條件
            PhraseQuery titleQuery = new PhraseQuery();//標題查詢條件
            List<string> lstkw = LuceneHelper.PanGuSplitWord(kw);//對用戶輸入的搜索條件進行拆分。

            foreach (string word in lstkw)
            {
                query.Add(new Term("Content", word));//contains("Content",word)
                titleQuery.Add(new Term("Title", word));
            }
            query.SetSlop(100);//兩個詞的距離大於100(經驗值)就不放入搜索結果,由於距離太遠相關度就不高了

            BooleanQuery bq = new BooleanQuery();
            //Occur.Should 表示 Or , Must 表示 and 運算
            bq.Add(query, BooleanClause.Occur.SHOULD);
            bq.Add(titleQuery, BooleanClause.Occur.SHOULD);

            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);//盛放查詢結果的容器
            searcher.Search(bq, null, collector);//使用query這個查詢條件進行搜索,搜索結果放入collector

            int recCount=collector.GetTotalHits();//總的結果條數
            ScoreDoc[] docs = collector.TopDocs((pageNo - 1) * pageLen, pageNo*pageLen).scoreDocs;//從查詢結果中取出第m條到第n條的數據

            List<SearchResult> list = new List<SearchResult>();
            string msg = string.Empty;
            string title = string.Empty;

            for (int i = 0; i < docs.Length; i++)//遍歷查詢結果
            {
                int docId = docs[i].doc;//拿到文檔的id,由於Document可能很是佔內存(思考DataSet和DataReader的區別)
                //因此查詢結果中只有id,具體內容須要二次查詢
                Document doc = searcher.Doc(docId);//根據id查詢內容。放進去的是Document,查出來的仍是Document
                SearchResult result = new SearchResult();
                result.Id = Convert.ToInt32(doc.Get("Id"));
                msg = doc.Get("Content");//只有 Field.Store.YES的字段才能用Get查出來
                result.Msg = LuceneHelper.CreateHightLight(kw, msg);//將搜索的關鍵字高亮顯示。
                title = doc.Get("Title");
                foreach (string word in lstkw)
                {
                    title=title.Replace(word,"<span style='color:red;'>"+word+"</span>");
                }
                //result.Title=LuceneHelper.CreateHightLight(kw, title);
                result.Title = title;
                result.CreateTime = Convert.ToDateTime(doc.Get("CreateTime"));
                result.Url = "/Article/Details?Id=" + result.Id + "&kw=" + kw;
                list.Add(result);
            }
            //先將搜索的詞插入到明細表。
            SearchDetail _SearchDetail = new SearchDetail { Id = Guid.NewGuid(), KeyWords = kw, SearchDateTime = DateTime.Now };
            db.SearchDetail.Add(_SearchDetail);
            int r = db.SaveChanges();

            PagedList<SearchResult> lst = new PagedList<SearchResult>(list, pageNo, pageLen, recCount);
            lst.TotalItemCount = recCount;
            lst.CurrentPageIndex = pageNo;

            return lst;
        }
        //或查詢
        PagedList<SearchResult> OrSearch(String kw, int pageNo, int pageLen = 4)
        {
            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());
            IndexReader reader = IndexReader.Open(directory, true);
            IndexSearcher searcher = new IndexSearcher(reader);
            List<PhraseQuery> lstQuery = new List<PhraseQuery>();
            List<string> lstkw = LuceneHelper.PanGuSplitWord(kw);//對用戶輸入的搜索條件進行拆分。

            foreach (string word in lstkw)
            {
                PhraseQuery query = new PhraseQuery();//查詢條件
                query.SetSlop(100);//兩個詞的距離大於100(經驗值)就不放入搜索結果,由於距離太遠相關度就不高了
                query.Add(new Term("Content", word));//contains("Content",word)

                PhraseQuery titleQuery = new PhraseQuery();//查詢條件
                titleQuery.Add(new Term("Title", word));

                lstQuery.Add(query);
                lstQuery.Add(titleQuery);
            }
          
            BooleanQuery bq = new BooleanQuery();
            foreach (var v in lstQuery)
            {
                //Occur.Should 表示 Or , Must 表示 and 運算
                bq.Add(v, BooleanClause.Occur.SHOULD);
            }
            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);//盛放查詢結果的容器
            searcher.Search(bq, null, collector);//使用query這個查詢條件進行搜索,搜索結果放入collector

            int recCount = collector.GetTotalHits();//總的結果條數
            ScoreDoc[] docs = collector.TopDocs((pageNo - 1) * pageLen, pageNo * pageLen).scoreDocs;//從查詢結果中取出第m條到第n條的數據

            List<SearchResult> list = new List<SearchResult>();
            string msg = string.Empty;
            string title = string.Empty;

            for (int i = 0; i < docs.Length; i++)//遍歷查詢結果
            {
                int docId = docs[i].doc;//拿到文檔的id,由於Document可能很是佔內存(思考DataSet和DataReader的區別)
                //因此查詢結果中只有id,具體內容須要二次查詢
                Document doc = searcher.Doc(docId);//根據id查詢內容。放進去的是Document,查出來的仍是Document
                SearchResult result = new SearchResult();
                result.Id = Convert.ToInt32(doc.Get("Id"));
                msg = doc.Get("Content");//只有 Field.Store.YES的字段才能用Get查出來
                result.Msg = LuceneHelper.CreateHightLight(kw, msg);//將搜索的關鍵字高亮顯示。
                title = doc.Get("Title");
                foreach (string word in lstkw)
                {
                    title = title.Replace(word, "<span style='color:red;'>" + word + "</span>");
                }
                //result.Title=LuceneHelper.CreateHightLight(kw, title);
                result.Title = title;
                result.CreateTime = Convert.ToDateTime(doc.Get("CreateTime"));
                result.Url = "/Article/Details?Id=" + result.Id + "&kw=" + kw;
                list.Add(result);
            }
            //先將搜索的詞插入到明細表。
            SearchDetail _SearchDetail = new SearchDetail { Id = Guid.NewGuid(), KeyWords = kw, SearchDateTime = DateTime.Now };
            db.SearchDetail.Add(_SearchDetail);
            int r = db.SaveChanges();

            PagedList<SearchResult> lst = new PagedList<SearchResult>(list, pageNo, pageLen, recCount);
            lst.TotalItemCount = recCount;
            lst.CurrentPageIndex = pageNo;

            return lst;
        }

        /// <summary>
        /// 獲取客戶列表 模糊查詢
        /// </summary>
        /// <param name="term"></param>
        /// <returns></returns>
        public string GetKeyWordsList(string term)
        {
            if (string.IsNullOrWhiteSpace(term))
                return null;

            var list = new KeyWordsTotalService().GetSearchMsg(term);
            //序列化對象
            //儘可能不要用JavaScriptSerializer,爲何?性能差,徹底可用Newtonsoft.Json來代替
            //System.Web.Script.Serialization.JavaScriptSerializer js = new System.Web.Script.Serialization.JavaScriptSerializer();
            //return js.Serialize(list.ToArray());
            return JsonConvert.SerializeObject(list.ToArray());
        }
View Code

至此,站內搜索的基本功能均已完成。

相關文章
相關標籤/搜索