Go html/template 使用入门
Go html/template 使用入门
用
html/template把 HTML 文件 + Go 数据 → 渲染成完整 HTML,并自动防 XSS。
一、模板是干嘛的
直接拼字符串:
html:="<p>你好 "+name+",验证码是 "+code+"</p>"两个大问题:
- 不安全:
name = "<script>alert(1)</script>"直接注入(XSS)。 - 不好维护: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><script>alert(1)</script></p>浏览器看到的是字符串,不会执行。
6.1 故意输出原始 HTML
用template.HTML类型告诉模板"我知道我在干啥,别转义":
data:=map[string]any{"Body":template.HTML(`<b>已经处理过的安全 HTML</b>`),}⚠️ 慎用!只有 100% 确认不来自用户输入时才用,否则就是 XSS 漏洞。
类似的:template.JS、template.URL、template.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 name | 用hello.html,或{{define "xxx"}}命名 |
| 中文乱码 | 没<meta charset="UTF-8"> | 模板首行加上 |
| 字段读不到 | struct 字段小写(不导出)/ map 键拼错 | 字段大写;map 键名要精确 |
输出多了一堆<> | 普通 string 默认转义 | 确认安全后用template.HTML包一下 |
Funcs不生效 | 在Parse之后才注册 | 必须New().Funcs().Parse()顺序 |
| 修改模板不生效 | 模板被缓存 | 开发期重新解析或用热重载 |
九、一句话总结
HTML 渲染永远用
html/template:ParseFiles/ParseGlob解析,Execute渲染,{{.X}}取数据,{{if}}{{range}}{{define}}控制结构,自动按上下文转义防 XSS。