Go html/template 使用入门

Go html/template 使用入门

html/template把 HTML 文件 + Go 数据 → 渲染成完整 HTML,并自动防 XSS。


一、模板是干嘛的

直接拼字符串:

html:="<p>你好 "+name+",验证码是 "+code+"</p>"

两个大问题:

  1. 不安全name = "<script>alert(1)</script>"直接注入(XSS)。
  2. 不好维护:HTML 长了之后改样式都得改 Go 代码。

模板= 把 HTML 放在.html文件里,留几个空({{.Name}}),运行时填数据。

模板文件 + 数据 → 渲染 → 完整 HTML 字符串

二、html/templatevstext/template

用途自动转义
text/template任意纯文本(配置、SQL)❌ 不转义
html/template网页、HTML 邮件按上下文自动转义,防 XSS

只要输出会被浏览器渲染,一律用html/template。它能识别上下文:

  • <p>{{.X}}</p>按 HTML 规则转义
  • <a href="{{.X}}">按 URL 规则转义
  • <script>var x = "{{.X}}";</script>按 JS 字符串规则转义

三、5 分钟跑通

3.1 模板文件

<!-- templates/hello.html --><!DOCTYPEhtml><html><head><metacharset="UTF-8"></head><body><p>你好 {{.Name}},</p><p>你的验证码是<b>{{.Code}}</b>,{{.ExpireMinutes}} 分钟内有效。</p></body></html>

{{.Name}}这种双花括号叫动作 (action)

3.2 渲染代码

packagemainimport("html/template""os")funcmain(){// 1. 解析模板tpl,err:=template.ParseFiles("templates/hello.html")iferr!=nil{panic(err)}// 2. 准备数据(map 或 struct 都行)data:=map[string]any{"Name":"Alice","Code":"123456","ExpireMinutes":5,}// 3. 渲染(也可以渲染到 http.ResponseWriter / bytes.Buffer)iferr:=tpl.Execute(os.Stdout,data);err!=nil{panic(err)}

// 渲染bytes.Buffer案例

varbuf bytes.Bufferiferr:=tpl.Execute(&buf,data);err!=nil{panic(err)}}

四、解析方式

// 单文件tpl,_:=template.ParseFiles("a.html")// 多文件tpl,_:=template.ParseFiles("a.html","b.html")// glob 通配tpl,_:=template.ParseGlob("templates/*.html")// 字符串tpl,_:=template.New("t").Parse(`<p>Hi {{.Name}}</p>`)// 解析失败直接 panic(启动期 fail fast)tpl:=template.Must(template.ParseGlob("templates/*.html"))

多模板渲染指定名字

varbuf bytes.Buffer tpl.ExecuteTemplate(&buf,"hello.html",data)// 按 base name

五、模板语法速查

5.1 取数据

<p>你好 {{.Name}}</p><p>邮箱 {{.Email}}</p>

.代表整个 data。.Name在 struct 里就是字段,在 map 里就是键。

⚠️struct 字段必须首字母大写(导出),否则模板看不见。

5.2 条件判断

{{if .IsVIP}}<span>尊敬的 VIP 用户</span>{{else if .IsNew}}<span>新用户</span>{{else}}<span>普通用户</span>{{end}}

判断"非空"用if .X,零值(空串、0、nil、false)会走 else。

5.3 循环

<ul>{{range .Items}}<li>{{.Title}} - ¥{{.Price}}</li>{{else}}<li>暂无数据</li>{{end}}</ul>

range内部的.自动变成当前迭代项。想拿外层数据用$

{{range .Items}}<li>{{$.UserName}} 买了 {{.Title}}</li>{{end}}

要 index:

{{range $i, $item := .Items}}<li>{{$i}}. {{$item.Title}}</li>{{end}}

5.4 变量

{{$name := .User.Name}}<p>{{$name}}</p>

5.5 with(缩小作用域)

{{with .User}}<p>{{.Name}} - {{.Email}}</p>{{end}}

with块里.变成.User

5.6 管道与函数

<p>{{.Name | printf "Hello, %s"}}</p>

|把左边传给右边的函数。

内置函数:lenprintprintfhtmljsurlqueryeqneltgtandornot

{{if eq .Status 1}}已支付{{end}} {{if and .IsVIP (gt .Age 18)}}...{{end}}

5.7 自定义函数

funcs:=template.FuncMap{"upper":strings.ToUpper,"fmtDate":func(t time.Time)string{returnt.Format("2006-01-02")},}tpl,_:=template.New("hello").Funcs(funcs).ParseFiles("templates/hello.html")

⚠️Funcs必须在Parse之前调,否则模板里的函数找不到。

模板里用:

<p>{{.Name | upper}}</p><p>{{fmtDate .CreatedAt}}</p>

六、自动转义(最值钱的特性)

data:=map[string]any{"Comment":`<script>alert(1)</script>`,}
<p>{{.Comment}}</p>

渲染结果:

<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>

浏览器看到的是字符串,不会执行

6.1 故意输出原始 HTML

template.HTML类型告诉模板"我知道我在干啥,别转义":

data:=map[string]any{"Body":template.HTML(`<b>已经处理过的安全 HTML</b>`),}

⚠️ 慎用!只有 100% 确认不来自用户输入时才用,否则就是 XSS 漏洞。

类似的:template.JStemplate.URLtemplate.CSS


七、抽取公共片段:define+template

<!-- _header.html -->{{define "header"}}<tablewidth="100%"><tr><td><h1>{{.AppName}}</h1></td></tr></table>{{end}}
<!-- page.html -->{{template "header" .}}<p>欢迎 {{.Name}}</p>

{{template "header" .}}调用名为header的子模板,.把当前数据传过去。

7.1 母版页(block)

<!-- layout.html --><html><body>{{block "content" .}}默认内容{{end}}</body></html>
<!-- page.html -->{{define "content"}}<p>这是 page 的内容</p>{{end}}
tpl,_:=template.ParseFiles("layout.html","page.html")tpl.ExecuteTemplate(w,"layout.html",data)

八、新手常踩的坑

现象原因解决
template ... not defined名字不是文件路径而是 base namehello.html,或{{define "xxx"}}命名
中文乱码<meta charset="UTF-8">模板首行加上
字段读不到struct 字段小写(不导出)/ map 键拼错字段大写;map 键名要精确
输出多了一堆&lt;&gt;普通 string 默认转义确认安全后用template.HTML包一下
Funcs不生效Parse之后才注册必须New().Funcs().Parse()顺序
修改模板不生效模板被缓存开发期重新解析或用热重载

九、一句话总结

HTML 渲染永远用html/templateParseFiles/ParseGlob解析,Execute渲染,{{.X}}取数据,{{if}}{{range}}{{define}}控制结构,自动按上下文转义防 XSS。