去提问»
提问背景 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 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代码高亮方案

发布时间:2026-08-18 13:27 👁️ 阅读:10

你问得非常对。既然Markdown的解析已经在后端进行,那么代码块(由``````标识)的识别和渲染也应该在后端完成,并生成带有高亮信息的HTML。前端只需要负责引入相应的CSS样式,而不需要再次解析和处理代码块。

这样做的好处是:

  1. 一致性:前后端渲染结果完全一致,不会因为不同解析器或库的差异导致显示问题。
  2. 性能:避免前端重复解析Markdown,减轻浏览器负担。
  3. SEO友好:代码内容直接在服务器端渲染成HTML,对搜索引擎更友好。

后端实现步骤 (Go + Goldmark + Chroma)

goldmark 本身是一个Markdown解析器,它提供了扩展机制来集成代码高亮功能。Go 社区中常用的代码高亮库是 chroma,它与 goldmark 有一个官方的集成扩展:github.com/yuin/goldmark-highlighting

1. 安装必要的库

首先,你需要安装 goldmark-highlightingchroma

go get github.com/yuin/goldmark-highlighting/v2
go get github.com/alecthomas/chroma/formatters/html
go get github.com/alecthomas/chroma/styles

2. 配置 Goldmark 实例

你需要修改你的 ConvertMarkdown 函数,或者更好的是,在程序启动时全局配置一个 goldmark.Markdown 实例。

package main

import (
	"bytes"
	"html/template"
	"log"

	"github.com/yuin/goldmark"
	highlighting "github.com/yuin/goldmark-highlighting/v2" // 注意这里的 v2 导入
	"github.com/alecthomas/chroma/formatters/html"
)

// 全局 Goldmark 实例,在程序启动时初始化一次
var md goldmark.Markdown

func init() {
	// 配置 Goldmark,启用代码高亮扩展
	// highlighting.WithStyle("github"): 选择一个Chroma内置的高亮风格,例如"github"、"monokai"、"dracula"等。
	// highlighting.WithFormatters(html.New(html.WithClasses(true))): 让Chroma生成带有CSS类名的HTML,而不是内联样式。
	md = goldmark.New(
		goldmark.WithExtensions(
			highlighting.NewHighlighting(
				highlighting.WithStyle("github"), // 示例:使用 GitHub 主题风格
				highlighting.WithFormatters(
					html.New(html.WithClasses(true)), // 输出带有 CSS 类名的 HTML
				),
			),
		),
		// 你也可以在这里添加其他 Goldmark 选项,例如:
		// goldmark.WithRendererOptions(
		// 	html.WithHardWraps(), // 自动换行
		// 	html.WithUnsafe(),    // 允许渲染原始HTML(需谨慎,可能存在XSS风险)
		// ),
	)
}

// ConvertMarkdown 函数现在会使用配置好的 Goldmark 实例来处理Markdown
func ConvertMarkdown(mdContent string) template.HTML {
	var buf bytes.Buffer
	// 使用全局 Goldmark 实例进行转换
	if err := md.Convert([]byte(mdContent), &buf); err != nil {
		log.Printf("Markdown转换失败: %v, 回退返回原始内容.", err)
		return template.HTML(mdContent) // 如果解析失败,回退返回原始文本
	}
	return template.HTML(buf.String())
}

// 你的 Baike_detail 函数保持不变,它会调用 ConvertMarkdown
// ...

3. 前端引入 Chroma CSS

后端使用 highlighting.WithStyle("github")html.New(html.WithClasses(true)) 配置后,chroma 会在生成的HTML代码块中添加特定的CSS类名(例如 class="language-go"class="chroma" 等)。为了让这些类名生效并显示正确的颜色和样式,你需要在前端页面中引入对应的 Chroma CSS 样式表。

你可以通过一个简单的 Go 程序来生成你所需风格的 Chroma CSS,然后将其保存为一个 .css 文件,并在HTML模板中引用。

生成 Chroma CSS 的 Go 示例:

package main

import (
	"fmt"
	"io/ioutil"
	"log"

	"github.com/alecthomas/chroma/formatters/html"
	"github.com/alecthomas/chroma/styles"
)

func generateChromaCSS(styleName string, outputPath string) error {
	formatter := html.New(html.WithClasses(true))
	style := styles.Get(styleName)
	if style == nil {
		return fmt.Errorf("未知样式: %s", styleName)
	}

	var buf bytes.Buffer
	if err := formatter.WriteCSS(&buf, style); err != nil {
		return fmt.Errorf("写入CSS失败: %w", err)
	}

	return ioutil.WriteFile(outputPath, buf.Bytes(), 0644)
}

func main() {
	// 在你的应用启动时或通过一个独立的脚本运行此函数一次
	// 例如,生成 'github' 风格的 CSS 文件
	err := generateChromaCSS("github", "./static/css/chroma-github.css")
	if err != nil {
		log.Fatalf("生成 Chroma CSS 失败: %v", err)
	}
	fmt.Println("Chroma CSS 文件已生成到 ./static/css/chroma-github.css")

	// ... 你可以在这里启动你的 Web 服务器 ...
}

HTML 模板中引入 CSS:

在你的 baike_detail.html 或其他公共 HTML 头部中,添加对生成CSS文件的引用:

<!DOCTYPE html>
<html>
<head>
    <title>{{.Article.Title}}</title>
    <!-- 引入生成的 Chroma CSS 文件 -->
    <link rel="stylesheet" href="/static/css/chroma-github.css">
    <!-- 其他你的CSS文件 -->
    <link rel="stylesheet" href="/static/css/your-main-styles.css">
</head>
<body>
    <!-- 页面内容 -->
    <div class="article-content">
        {{.Article.Content}}
    </div>
</body>
</html>

确保你的 Web 服务器正确配置了 /static/css/ 路径来提供这个 CSS 文件。

总结

  1. 后端处理goldmark 结合 goldmark-highlightingchroma 库,在后端将 Markdown 文本(包括代码块)解析并转换为带有高亮类名的 HTML。
  2. 前端配合:前端页面只需引入 chroma 生成的 CSS 样式文件,浏览器会自动根据HTML中的类名应用高亮样式。

这样,你的后端就可以完整地处理 Markdown 渲染和代码高亮,前端仅负责样式呈现,达到了最佳的实践效果。