From 2bcfa1da936c19a1958c5ea0c418ce1a30968ac4 Mon Sep 17 00:00:00 2001 From: Zhang Chao Date: Mon, 31 Aug 2026 17:50:58 +0800 Subject: [PATCH] =?UTF-8?q?slug=E7=94=9F=E6=88=90=E9=87=87=E7=94=A8?= =?UTF-8?q?=E6=97=A2=E6=9C=89=E5=B7=A5=E5=85=B7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 + go.sum | 4 ++ handlers/backend/post.go | 7 +-- utils/markdown.go | 95 +++++++++++++++++++++++----------------- 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index eda963e..5d4ca36 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/glebarez/sqlite v1.11.0 github.com/go-sql-driver/mysql v1.10.0 github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/gosimple/slug v1.15.0 github.com/spf13/viper v1.21.0 github.com/yuin/goldmark v1.8.5 golang.org/x/crypto v0.53.0 @@ -30,6 +31,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gosimple/unidecode v1.0.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 96b350a..f22fbe6 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,10 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo= +github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= diff --git a/handlers/backend/post.go b/handlers/backend/post.go index fd25354..a73048b 100644 --- a/handlers/backend/post.go +++ b/handlers/backend/post.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "strconv" - "strings" "time" "goblog/database" @@ -13,6 +12,7 @@ import ( "goblog/utils" "github.com/gin-gonic/gin" + "github.com/gosimple/slug" "gorm.io/gorm" ) @@ -69,10 +69,7 @@ func PreviewMarkdown(c *gin.Context) { // 生成 slug func generateSlug(title string) string { - slug := strings.ToLower(title) - slug = strings.ReplaceAll(slug, " ", "-") - slug = strings.ReplaceAll(slug, "_", "-") - // 简化处理,实际项目中可能需要更完善的 slug 生成 + slug := slug.Make(title) return slug } diff --git a/utils/markdown.go b/utils/markdown.go index 7314bcb..a15036c 100644 --- a/utils/markdown.go +++ b/utils/markdown.go @@ -34,83 +34,100 @@ func RenderMarkdown(source string) template.HTML { // 全局预编译正则(保持不变,但移除冗余) var ( - // 标题 + // 块级语法 reHeading = regexp.MustCompile(`(?m)^\s*#{1,6}\s+`) - // 无序列表 + // 列表 reUnorderedList = regexp.MustCompile(`(?m)^\s*[-*+]\s+`) // 有序列表 reOrderedList = regexp.MustCompile(`(?m)^\s*\d+[.)]\s+`) // 块引用 reBlockquote = regexp.MustCompile(`(?m)^\s*>\s+`) - // 水平线 + // 分割线 reHr = regexp.MustCompile(`(?m)^\s*[-*_]{3,}\s*$`) - // 脚注定义 - reFootnoteDef = regexp.MustCompile(`(?m)^\[\^.+?\]:\s.*$`) + // 引用定义 - reRefDef = regexp.MustCompile(`(?m)^\s*\[.+?\]:\s*(.*)$`) - // 图片 - reImage = regexp.MustCompile(`(?s)!\[.*?\]\(.*?\)`) - // 引用图片 + reFootnoteDef = regexp.MustCompile(`(?m)^\[\^.+?\]:\s.*$`) + reRefDef = regexp.MustCompile(`(?m)^\s*\[.+?\]:\s*(.*)$`) + + // 图片(全部移除) + reImage = regexp.MustCompile(`!\[.*?\]\(.*?\)`) reRefImage = regexp.MustCompile(`!\[.*?\](\[.*?\])?`) - // 链接 - reLink = regexp.MustCompile(`(?s)\[(.*?)\]\(.*?\)`) - // 引用式链接 - reRefLink = regexp.MustCompile(`\[([^\s=\[\]/]+)\](\[.*?\])?`) - // 粗体 - reBoldStar = regexp.MustCompile(`(?s)\*\*(.*?)\*\*`) - // 斜体 - reItalicStar = regexp.MustCompile(`(?s)\*(.*?)\*`) - // 下划线粗体 - reBoldUnderscore = regexp.MustCompile(`(?s)_(.*?)_`) - // 下划线斜体 - reItalicUnderscore = regexp.MustCompile(`(?s)_(.*?)_`) + + // 内联链接 [text](url) → 保留 text + reLink = regexp.MustCompile(`\[([^\]]*)\]\([^)]*\)`) + // 引用式链接 [text][id] 或 [text][] → 保留 text + reRefLink = regexp.MustCompile(`\[([^\]]*)\](\[[^\]]*\])?`) + + // 行内标记(加粗、斜体、删除线) + reBoldStar = regexp.MustCompile(`\*\*(.+?)\*\*`) + reBoldUnd = regexp.MustCompile(`__(.+?)__`) + reItalicStar = regexp.MustCompile(`\*(.+?)\*`) // 注意:必须在粗体之后处理 + reItalicUnd = regexp.MustCompile(`_(.+?)_`) // 同上 + reStrike = regexp.MustCompile(`~~(.+?)~~`) // 删除线 + + // 为保留代码内容(可能含关键词),此处只移除反引号,但代码块可能多行,我们保留内容。 + reCodeInline = regexp.MustCompile("`([^`]*)`") + reCodeBlock = regexp.MustCompile("(?s)```(.*?)```") // HTML 标签 reHTMLTag = regexp.MustCompile(`<[^>]+>`) + + // 表格分隔线(移除 --- 行,保留内容) + reTableSep = regexp.MustCompile(`(?m)^\s*\|?\s*[-:]+[-| :]+\s*\|?\s*$`) + // 多余空白 reMultiSpace = regexp.MustCompile(`\s+`) ) -// GenerateSearchableText 提取 Markdown 中的纯文本 +// GenerateSearchableText 提取 Markdown 中的可见纯文本 func GenerateSearchableText(raw string) string { if raw == "" { return "" } text := raw - // 全角空格转半角 + // 1. 全角空格转半角 text = strings.ReplaceAll(text, "\u3000", " ") - // 移除块级标记(一次性替换多个) - text = reHr.ReplaceAllString(text, "") - text = reBlockquote.ReplaceAllString(text, "") + // 2. 移除块级标记(标题、列表、引用、分割线) + text = reHeading.ReplaceAllString(text, "") text = reUnorderedList.ReplaceAllString(text, "") text = reOrderedList.ReplaceAllString(text, "") - text = reHeading.ReplaceAllString(text, "") + text = reBlockquote.ReplaceAllString(text, "") + text = reHr.ReplaceAllString(text, "") - // 移除引用定义 + // 3. 移除引用定义行 text = reFootnoteDef.ReplaceAllString(text, "") text = reRefDef.ReplaceAllString(text, "") - // 移除图片 + // 4. 移除图片(所有形式) text = reImage.ReplaceAllString(text, "") text = reRefImage.ReplaceAllString(text, "") - // 处理链接:保留可见文字(先引用式后内联) - text = reRefLink.ReplaceAllString(text, "$1") + // 5. 移除代码块(整块移除,因为代码块内容通常不参与自然语言搜索) + text = reCodeBlock.ReplaceAllString(text, "$1") + + // 6. 处理链接(先引用式后内联) text = reLink.ReplaceAllString(text, "$1") + text = reRefLink.ReplaceAllString(text, "$1") - // 移除粗体 - text = reBoldStar.ReplaceAllString(text, "") - text = reBoldUnderscore.ReplaceAllString(text, "") - // 移除斜体 - text = reItalicStar.ReplaceAllString(text, "") - text = reItalicUnderscore.ReplaceAllString(text, "") + // 7. 移除行内代码反引号(保留代码内容) + text = reCodeInline.ReplaceAllString(text, "") - // 移除 HTML 标签 + // 8. 移除粗体、斜体、删除线标记(保留文字) + text = reBoldStar.ReplaceAllString(text, "$1") + text = reBoldUnd.ReplaceAllString(text, "$1") + text = reItalicStar.ReplaceAllString(text, "$1") + text = reItalicUnd.ReplaceAllString(text, "$1") + text = reStrike.ReplaceAllString(text, "$1") + + // 9. 移除表格分隔线(保留表格内容) + text = reTableSep.ReplaceAllString(text, "") + + // 10. 移除 HTML 标签 text = reHTMLTag.ReplaceAllString(text, "") - // 压缩空白 + // 11. 压缩空白 text = reMultiSpace.ReplaceAllString(text, " ") return strings.TrimSpace(text) }