模板渲染:html/template 多模板、自定义函数、模板继承思路

Gin 渲染 HTML 默认由标准库 html/template 承担,它会在输出时自动做 HTML 转义,天然防 XSS。本章讲多模板加载、自定义函数与「模板继承」思路,API 以 pkg.go.dev 的 html/template 文档为准。

1. 多模板加载

把每个页面作为独立模板文件,然后一次性解析:

templates/
├── index.html
└── post.html
r := gin.Default()
r.LoadHTMLGlob("templates/*.html") // 逐个解析成命名模板

r.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.html", gin.H{
        "Title": "首页",
    })
})

LoadHTMLGlob 之后,c.HTML(code, 名称, data) 里的名称必须写文件基名,例如 index.html

2. 自定义函数

想在模板里格式化时间、拼接 URL,用 FuncMap 注入,且必须在解析模板之前:

func formatTime(t time.Time) string {
    return t.Format("2006-01-02 15:04")
}

tmpl := template.Must(template.New("").
    Funcs(template.FuncMap{"formatTime": formatTime}).
    ParseGlob("templates/*.html"))

r.SetHTMLTemplate(tmpl) // 覆盖默认解析结果

模板里即可写 {{ formatTime .CreatedAt }}。同一函数名重复注册会 panic。

3. 模板文件示例

<!-- templates/index.html -->
<h1>{{ .Title }}</h1>
<p>{{ .Content }}</p>
<p>{{ formatTime .CreatedAt }}</p>

{{ .Content }} 里的 <script> 等标签会被原样转义输出,不会当作 HTML 执行。

4. 模板继承思路

Go 模板没有 extends 关键字,常见做法是「公共布局 + 内容块」:布局文件用 {{define}}/{{block}} 声明区块,页面文件只填充区块:

<!-- templates/layout.html -->
{{define "layout"}}
<html><body>{{block "content" .}}默认内容{{end}}</body></html>
{{end}}
<!-- templates/index.html -->
{{define "content"}}<h1>{{ .Title }}</h1>{{end}}
tmpl := template.Must(template.ParseFiles(
    "templates/layout.html", "templates/index.html"))
r.SetHTMLTemplate(tmpl)
// 渲染时执行命名模板 "layout",content 区块被页面覆盖

另一种做法是让页面自带完整 HTML 壳、只复用公共片段(header/footer),社区也有 multitemplate 这类库做「路由到模板」的动态映射,可按需选用。

注意点

  • html/template 只转义 {{ }} 输出的内容;template.HTMLtemplate.JS 等类型会关闭转义,非可信来源不要用。
  • 模板里只能访问导出字段与已注册函数,不能调用任意方法。
  • 生产建议用 SetHTMLTemplate 一次构建;开发期 LoadHTMLGlob 每次全量解析尚可接受。
  • 配合 embed 部署时用 template.ParseFS(fs, "templates/*.html"),部署章节再展开。

小结

页面少用 LoadHTMLGlob + 文件基名;要自定义逻辑就 Funcs 注册函数再 SetHTMLTemplate;要公共壳就用 define/block 组装。自动转义是安全底线,不要轻易用 template.HTML 关掉它。

笔记加载中…