Go html/template 使用入门

Go html/template 使用入门

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


一、模板是干嘛的

直接拼字符串:

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

两个大问题:

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

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

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

二、html/template vs text/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 模板文件

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

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

3.2 渲染代码

go 复制代码
package main

import (
    "html/template"
    "os"
)

func main() {
    // 1. 解析模板
    tpl, err := template.ParseFiles("templates/hello.html")
    if err != nil {
        panic(err)
    }

    // 2. 准备数据(map 或 struct 都行)
    data := map[string]any{
        "Name":          "Alice",
        "Code":          "123456",
        "ExpireMinutes": 5,
    }

    // 3. 渲染(也可以渲染到 http.ResponseWriter / bytes.Buffer)
    if err := tpl.Execute(os.Stdout, data); err != nil {
        panic(err)
    }

// 渲染bytes.Buffer案例

go 复制代码
var buf bytes.Buffer
 if err := tpl.Execute(&buf, data); err != nil {
  panic(err)
 }
}

四、解析方式

go 复制代码
// 单文件
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"))

多模板渲染指定名字

go 复制代码
var buf bytes.Buffer
tpl.ExecuteTemplate(&buf, "hello.html", data) // 按 base name

五、模板语法速查

5.1 取数据

html 复制代码
<p>你好 {{.Name}}</p>
<p>邮箱 {{.Email}}</p>

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

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

5.2 条件判断

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

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

5.3 循环

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

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

html 复制代码
{{range .Items}}
  <li>{{$.UserName}} 买了 {{.Title}}</li>
{{end}}

要 index:

html 复制代码
{{range $i, $item := .Items}}
  <li>{{$i}}. {{$item.Title}}</li>
{{end}}

5.4 变量

html 复制代码
{{$name := .User.Name}}
<p>{{$name}}</p>

5.5 with(缩小作用域)

html 复制代码
{{with .User}}
  <p>{{.Name}} - {{.Email}}</p>
{{end}}

with 块里 . 变成 .User

5.6 管道与函数

html 复制代码
<p>{{.Name | printf "Hello, %s"}}</p>

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

内置函数:len print printf html js urlquery eq ne lt gt and or not

html 复制代码
{{if eq .Status 1}}已支付{{end}}
{{if and .IsVIP (gt .Age 18)}}...{{end}}

5.7 自定义函数

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

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

模板里用:

html 复制代码
<p>{{.Name | upper}}</p>
<p>{{fmtDate .CreatedAt}}</p>

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

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

渲染结果:

html 复制代码
<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>

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

6.1 故意输出原始 HTML

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

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

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

类似的:template.JStemplate.URLtemplate.CSS


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

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

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

7.1 母版页(block)

html 复制代码
<!-- layout.html -->
<html><body>
  {{block "content" .}}默认内容{{end}}
</body></html>
html 复制代码
<!-- page.html -->
{{define "content"}}
  <p>这是 page 的内容</p>
{{end}}
go 复制代码
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 键名要精确
输出多了一堆 &lt; &gt; 普通 string 默认转义 确认安全后用 template.HTML 包一下
Funcs 不生效 Parse 之后才注册 必须 New().Funcs().Parse() 顺序
修改模板不生效 模板被缓存 开发期重新解析或用热重载

九、一句话总结

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

相关推荐
geovindu1 小时前
java: Gale-Shapley Algorithm
java·开发语言·后端·算法
午安~婉1 小时前
挑战二:博客预览卡片
前端·javascript·html
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-34 通讯录的录入与显示(C语言实现)
c语言·开发语言·数据结构·算法
星核0penstarry2 小时前
DeepSeek-V4-Flash 正式公测:大模型行业进入「极速平价普惠时代」
java·开发语言·人工智能
老洋葱Mr_Onion2 小时前
【C++】高精度模板
开发语言·c++·算法
脚踏实地皮皮晨2 小时前
003003002_WPF Grid 基类官方类定义逐行深度解析
开发语言·windows·算法·c#·wpf·visual studio
xieliyu.2 小时前
Java 线程池入门详解:线程池基础定义 + ThreadPoolExecutor 全参构造方法逐参数解析
java·开发语言·idea·javaee
一条泥憨鱼2 小时前
【从0开始学习计算机网络】| HTTP方法疑点解析
开发语言·计算机网络·http