模板渲染: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.HTML、template.JS等类型会关闭转义,非可信来源不要用。- 模板里只能访问导出字段与已注册函数,不能调用任意方法。
- 生产建议用
SetHTMLTemplate一次构建;开发期LoadHTMLGlob每次全量解析尚可接受。 - 配合 embed 部署时用
template.ParseFS(fs, "templates/*.html"),部署章节再展开。
小结
页面少用 LoadHTMLGlob + 文件基名;要自定义逻辑就 Funcs 注册函数再 SetHTMLTemplate;要公共壳就用 define/block 组装。自动转义是安全底线,不要轻易用 template.HTML 关掉它。