重寫 StarBlog 的搜索功能和頁面,支持權重設置和結果高亮
前言
最近在整理本地的一些筆記。
有些日期不太對的,我的博客上有記錄發布和更新時間,所以我去搜索了一下。
這時候發現 StarBlog 的搜索功能太簡陋了。
雖然上次更新增加了一大波功能,也優化了一下搜索功能,之前只能搜索標題,現在可以搜索正文內容了。
不過有個問題是沒有權重,標題的權重應該比正文更高的。
按理說這些應該得加入全文檢索引擎,Elasticsearch、MeiliSearch[2] 之類的來實現。但這些需要額外的服務,太重了。
再不濟也要用 Lucene.NET[3] 這種,這是 Elasticsearch 的基礎,但不需要額外服務,純本地嵌入式,支持權重控制、高亮、分詞等功能。
但為了快速實現,這些我都不想用,先用最簡單的方式來改進。
同時我也重寫了搜索結果頁面,之前的頁面太業余了。
極簡實現
最終我的方案是:在內存里手動算權重 + Regex 實現結果高亮。
成本非常低,效果也不錯。
實現效果
來看看效果吧。
這套 StarBlog 的前端是 Bootstrap,樣式都得靠 CSS,相對于我現在用的 Tailwind CSS、Shadcn/ui、Magic UI 之類的,太原始了,重寫這個界面已經盡力了hhh??
image-20250902212504494
代碼
OK,接下來是大家不感興趣的代碼環節。
模型
先定義搜索結果模型。
public classSearchPost {
public Post Post { get; set; }
publicint TitleScore { get; set; }
publicint ContentScore { get; set; }
// 標題每命中一次+100分
// 內容命中+1分
publicint Score => TitleScore * 100 + ContentScore;
publicstring HighlightedTitle { get; set; }
publicstring HighlightedSnippet { get; set; }
}搜索邏輯
搜索功能邏輯都在 src/StarBlog.Web/Controllers/SearchController.cs 文件里。
在內村里計算權重,從數據庫查詢出來后,用 Linq 計算權重,關鍵詞出現一次為一分;總分是在 SearchPost 里計算的,標題每命中一次+100分,內容命中+1分。
var searchPosts = _postRepo
.Where(a => a.IsPublish)
.Where(a =>
a.Title!.Contains(keyword) ||
a.Content.Contains(keyword)
)
.Include(a => a.Category)
.ToList()
.Select(p => new SearchPost {
Post = p,
TitleScore = p.Title.ToLower().Split(keyword).Length - 1,
ContentScore = p.Content?.ToLower().Split(keyword).Length - 1 ?? 0,
})
.OrderByDescending(x => x.Score)
.ToList();搜索結果高亮
使用正則表達式來實現結果高亮。
var regex = new Regex(Regex.Escape(keyword), RegexOptions.IgnoreCase);
foreach (var item in searchPosts) {
item.HighlightedTitle = regex.Replace(item.Post.Title, m => $"<mark>{m.Value}</mark>");
item.HighlightedSnippet = GetHighlightedSnippet(item.Post.Content, keyword);
}生成高亮片段摘要
思路很簡單:
- 找到第一個命中的位置
- 截取前后一定長度的內容(比如前后各 50 個字符)
- 再用 Regex 替換加
<mark>高亮 - 最后拼上
...作為省略號
public static string GetHighlightedSnippet(string content, string keyword, int snippetLength = 100) {
if (string.IsNullOrEmpty(content) || string.IsNullOrEmpty(keyword))
returnstring.Empty;
var regex = new Regex(Regex.Escape(keyword), RegexOptions.IgnoreCase);
var match = regex.Match(content);
if (!match.Success) {
// 沒匹配到,直接取前 snippetLength*2 個字符作為摘要
return content.Length > snippetLength * 2
? content.Substring(0, snippetLength * 2) + "..."
: content;
}
// 計算截取范圍(匹配位置前后各 snippetLength)
int start = Math.Max(0, match.Index - snippetLength);
int length = Math.Min(content.Length - start, match.Length + snippetLength * 2);
string snippet = content.Substring(start, length);
// 高亮處理
snippet = regex.Replace(snippet, m => $"<mark>{m.Value}</mark>");
// 前后補省略號(如果不是全文開頭或結尾)
if (start > 0) snippet = "..." + snippet;
if (start + length < content.Length) snippet += "...";
return snippet;
}小結
重構之后體驗更上一層,不過在老架構上修修補補終究不是長久之計,等有空就得趕緊開始 v2 新版的開發。
參考資料
[1] StarBlog v1.3.0 新版本,一大波更新以及遷移服務器部署: https://blog.deali.cn/p/starblog-v130-updates-server-migration
[2] MeiliSearch: https://www.meilisearch.com/
[3] Lucene.NET: https://lucenenet.apache.org/























