提问背景 Question:
按你说的改了,后台没有改变什么,应该是后端的问题。后端代码如下。
// 2. 百科详情页 Handler
// 路由配置:r.GET("/baike/article/:id", ai.Baike_detail)
func Baike_detail(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
articleIDStr := ps.ByName("id")
articleID, err := strconv.Atoi(articleIDStr)
if err != nil {
http.NotFound(w, r)
return
}
// 1. 查询文章主体
var a Article
var descriptionRaw string // 用临时变量接收数据库中的原始 String
var contentRaw string
var createdAtInt int64
row := fun.DB.QueryRow(
"SELECT id, question, title, description, content, click_count, like_count, created_at FROM cms_article WHERE id = ? AND status = 1",
articleID,
)
err = row.Scan(&a.ID, &a.Question, &a.Title, &descriptionRaw, &contentRaw, &a.ClickCount, &a.LikeCount, &createdAtInt)
if err != nil {
if err == sql.ErrNoRows {
http.NotFound(w, r)
} else {
http.Error(w, "数据库错误: "+err.Error(), http.StatusInternalServerError)
}
return
}
a.Description = ConvertMarkdown(descriptionRaw) // 解析提问背景的 Markdown
a.Content = ConvertMarkdown(contentRaw) //解析Markdown并转义template.HTML
a.CreatedAt = time.Unix(createdAtInt, 0).Format("2006-01-02 15:04")
// 2. 异步增加浏览量(避免阻塞主渲染)
go func(id int) {
_, _ = fun.DB.Exec("UPDATE cms_article SET click_count = click_count + 1 WHERE id = ?", id)
}(articleID)
// 3. 查询当前文章的 Tags
var tags []Tag
tagRows, err := fun.DB.Query(`
SELECT t.id, t.name
FROM cms_tag t
INNER JOIN cms_article_tag at ON t.id = at.tag_id
WHERE at.article_id = ?`,
articleID,
)
if err == nil {
for tagRows.Next() {
var t Tag
if err := tagRows.Scan(&t.ID, &t.Name); err == nil {
tags = append(tags, t)
}
}
tagRows.Close()
}
data := BaikeDetailData{
Article: a,
Tags: tags,
}
// 4. 渲染模板
path := filepath.Join(fun.AppPath, "/templates/ai/baike_detail.html")
mb, err := fun.TManager.Get(path)
if err != nil {
http.Error(w, "详情模板渲染失败: "+err.Error(), http.StatusInternalServerError)
return
}
err = mb.Execute(w, data)
if err != nil {
fun.WriteLog("渲染百科详情页模板报错: %s", err)
}
}
/*
func ConvertMarkdown(mdContent string) template.HTML {
var buf bytes.Buffer
if err := goldmark.Convert([]byte(mdContent), &buf); err != nil {
return template.HTML(mdContent) // 如果解析失败,回退返回原始文本
}
return template.HTML(buf.String())
}
*/
func ConvertMarkdown(mdContent string) template.HTML {
// 1. 将字面量的 "" (反斜杠+n) 替换为真正的换行符 ""
mdContent = strings.ReplaceAll(mdContent, "\\n", "\n")
// 2. 兼容性处理:将字面量的 "\\r" 替换为真正的 "\r"
mdContent = strings.ReplaceAll(mdContent, "\\r", "\n")
// 3. 兼容性处理:将 Windows 换行符统一替换为 Linux 换行符(可选,防患未然)
mdContent = strings.ReplaceAll(mdContent, "\r", "\n")
var buf bytes.Buffer
if err := goldmark.Convert([]byte(mdContent), &buf); err != nil {
return template.HTML(mdContent) // 如果解析失败,回退返回原始文本
}
return template.HTML(buf.String())
}
Markdown渲染问题排查
你好!根据你提供的后端代码,ConvertMarkdown 函数的实现看起来是正确的,它处理了 和 等转义字符,并使用 goldmark 库将 Markdown 转换为 HTML,最后返回 template.HTML 类型以防止在模板中被二次转义。
如果修改了 ConvertMarkdown 函数但前端显示没有变化,问题可能不在 ConvertMarkdown 函数本身,而在于以下几个方面:
- 数据库中存储的内容:
- 是否包含有效的 Markdown 语法?
ConvertMarkdown函数负责将 包含 Markdown 语法 的字符串转换为 HTML。如果description或content字段中存储的只是纯文本(例如,只有文字和简单的换行,没有## 标题、* 列表项、**粗体**等 Markdown 标记),那么即使ConvertMarkdown运行正常,它也只会将纯文本包裹在<p>标签中,这在视觉上可能与预期渲染的 Markdown 效果不同。 - 换行符存储方式:你之前的修改是为了处理
和这种 字面量转义 的换行符。请确认你的数据库中存储的换行符是(两个反斜杠加n),还是已经是实际的换行符(一个反斜杠加n),或者是 `
- 是否包含有效的 Markdown 语法?
。如果数据库中直接存储的是
(实际的换行符),那么strings.ReplaceAll(mdContent, "
", "
")这行代码就不会产生任何效果,但这对goldmark 渲染是没问题的,goldmark` 可以直接识别实际的换行符。关键是除了换行符,是否还有其他Markdown语法。
-
前端模板
baike_detail.html的使用方式:- 请检查
baike_detail.html模板中是如何输出{{.Article.Description}}和{{.Article.Content}}的。确保你没有在模板中误使用了| html或| safe等管道符再次对template.HTML类型的内容进行转义。例如,错误的用法可能是{{.Article.Content | html}},这会导致template.HTML再次被转义,显示为原始的 HTML 字符串而非渲染后的效果。 - 正确的用法应该直接是
{{.Article.Content}},因为template.HTML类型的数据在 Go 模板引擎中默认就不会被转义。
- 请检查
-
部署或重启问题:
- 请确保你的 Go 后端服务在修改代码后已经重新编译并部署,并且服务已重启。这是一个非常常见的疏忽,导致代码修改未能生效。
排查建议:
- 检查数据库内容:直接查看
cms_article表中description和content字段的原始内容,确认它们是否包含预期的 Markdown 语法(例如##、-、*等)。 - 日志输出:在
ConvertMarkdown函数的开头和结束处添加日志,打印mdContent转换前后的值,以及buf.String()的结果,例如:func ConvertMarkdown(mdContent string) template.HTML { fun.WriteLog("ConvertMarkdown Input: %s", mdContent) mdContent = strings.ReplaceAll(mdContent, "\
", "
")
mdContent = strings.ReplaceAll(mdContent, "
", "
")
mdContent = strings.ReplaceAll(mdContent, "
", "
")
fun.WriteLog("ConvertMarkdown Processed Input: %s", mdContent)
var buf bytes.Buffer
if err := goldmark.Convert([]byte(mdContent), &buf); err != nil {
fun.WriteLog("ConvertMarkdown Error: %s", err)
return template.HTML(mdContent)
}
fun.WriteLog("ConvertMarkdown Output HTML: %s", buf.String())
return template.HTML(buf.String())
}
```
通过日志查看 `goldmark` 实际接收到的内容和转换后的 HTML 是什么,这能帮助你定位问题是在输入内容上还是在 `goldmark` 的转换上。
- 浏览器开发者工具:在前端页面上,使用浏览器的开发者工具检查元素,查看
Article.Description或Article.Content对应的 HTML 元素内容。看看是显示为已经渲染的 HTML 结构,还是原始的 Markdown 文本,这能帮助判断问题是在后端转换,还是前端模板显示。