提问背景 Question:
我添加了解析md格式的函数,但好像没有效果,你看看下面的代码。// 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 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, &a.Description, &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.Content = template.HTML(contentRaw) // 转换为非转义 HTML,直接渲染
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())
}
解决 Go 百科 Markdown 解析不生效问题
你的 Markdown 解析函数 ConvertMarkdown 在逻辑上是完全正确的。如果在页面上**“没有效果”**(表现为直接显示了原始的 Markdown 语法,或者页面上直接把 HTML 标签 <p>、<h1> 当作纯文本显示了出来),通常是由以下两个原因导致的:
核心原因排查
原因 1:Article 结构体的 Content 字段类型定义为了 string
如果你的 Article 结构体定义中,Content 字段是 string 类型:
type Article struct {
ID int
Title string
Content string // 如果这里是 string!
// ...
}
那么即使你在 Go 代码里写了 a.Content = string(ConvertMarkdown(contentRaw)),在 Go 的 html/template 模板引擎渲染时,它依然会把 a.Content 当作普通字符串,自动将其中的 HTML 标签全部转义(例如把 <p> 转义为 <p>),导致浏览器无法解析成富文本。
【解决方案】
必须将 Article 结构体中的 Content 字段类型定义为 template.HTML:
import "html/template"
type Article struct {
ID int
Question string
Title string
Description string
Content template.HTML // 必须是 template.HTML 类型
ClickCount int
LikeCount int
CreatedAt string
}
原因 2:Goldmark 默认过滤了 HTML 标签(Unsafe 策略)
如果你的 Markdown 文本中本身夹杂了 HTML 标签,goldmark 默认出于安全考虑,会过滤或者转义这些 HTML。如果需要保留原生的 HTML 渲染,需要开启 WithUnsafe() 选项。
【解决方案】
修改你的 ConvertMarkdown 函数,引入 goldmark 的 renderer 选项:
import (
"bytes"
"html/template"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/renderer"
)
func ConvertMarkdown(mdContent string) template.HTML {
var buf bytes.Buffer
// 创建一个支持渲染原生 HTML 的 goldmark 实例
md := goldmark.New(
goldmark.WithRendererOptions(
renderer.WithUnsafe(), // 允许渲染未安全过滤的 HTML 标签
),
)
if err := md.Convert([]byte(mdContent), &buf); err != nil {
return template.HTML(mdContent)
}
return template.HTML(buf.String())
}
原因 3:HTML 模板中的标签调用方式不正确
在你的 baike_detail.html 模板中,输出内容的地方需要确保直接输出了该字段。因为在 Go 后端已经把 Article.Content 包装为了 template.HTML 安全类型,模板里直接调用即可,不需要添加额外的转义处理:
<!-- 在你的 HTML 模板中 -->
<div class="prose max-w-none">
{{ .Article.Content }}
</div>
总结排查清单
- 检查
Article结构体的Content字段类型是否为template.HTML。 - 检查
import中是否引入了html/template而不是text/template。 - 如果 Markdown 中有 HTML 标签,确保使用了
renderer.WithUnsafe()配置。