去提问»
提问背景 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渲染问题排查

发布时间:2026-08-18 15:13 👁️ 阅读:64

你好!根据你提供的后端代码,ConvertMarkdown 函数的实现看起来是正确的,它处理了 等转义字符,并使用 goldmark 库将 Markdown 转换为 HTML,最后返回 template.HTML 类型以防止在模板中被二次转义。

如果修改了 ConvertMarkdown 函数但前端显示没有变化,问题可能不在 ConvertMarkdown 函数本身,而在于以下几个方面:

  1. 数据库中存储的内容
    • 是否包含有效的 Markdown 语法? ConvertMarkdown 函数负责将 包含 Markdown 语法 的字符串转换为 HTML。如果 descriptioncontent 字段中存储的只是纯文本(例如,只有文字和简单的换行,没有 ## 标题* 列表项**粗体** 等 Markdown 标记),那么即使 ConvertMarkdown 运行正常,它也只会将纯文本包裹在 <p> 标签中,这在视觉上可能与预期渲染的 Markdown 效果不同。
    • 换行符存储方式:你之前的修改是为了处理 这种 字面量转义 的换行符。请确认你的数据库中存储的换行符是 (两个反斜杠加n),还是已经是实际的换行符 (一个反斜杠加n),或者是 `

。如果数据库中直接存储的是
(实际的换行符),那么strings.ReplaceAll(mdContent, "
", "
")这行代码就不会产生任何效果,但这对goldmark 渲染是没问题的,goldmark` 可以直接识别实际的换行符。关键是除了换行符,是否还有其他Markdown语法。

  1. 前端模板 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 模板引擎中默认就不会被转义。
  2. 部署或重启问题

    • 请确保你的 Go 后端服务在修改代码后已经重新编译并部署,并且服务已重启。这是一个非常常见的疏忽,导致代码修改未能生效。

排查建议:

  • 检查数据库内容:直接查看 cms_article 表中 descriptioncontent 字段的原始内容,确认它们是否包含预期的 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.DescriptionArticle.Content 对应的 HTML 元素内容。看看是显示为已经渲染的 HTML 结构,还是原始的 Markdown 文本,这能帮助判断问题是在后端转换,还是前端模板显示。