Go 模板初识
🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
❄️个人专栏:数据结构与算法,数据库,leetcode
✨那些你一个人走过的夜路,终将化作照亮未来的光
目标:搞清楚模板是什么、标准库怎么解析与执行、
text/template和html/template的区别,并能写出第一个可运行的渲染示例。
1. 为什么需要模板?
做 Web 或生成报告时,经常要把动态数据填进一段相对固定的文本里。
如果不使用模板,很容易写成这样:
html := "<h1>Hello, " + user.Name + "</h1>" html += "<p>年龄:" + strconv.Itoa(user.Age) + "</p>"问题很明显:
- 难维护:HTML 结构和 Go 代码缠在一起
- 易出错:引号、转义、换行一多就乱
- 不安全:用户输入直接拼进 HTML,可能造成 XSS
- 难复用:页头页脚无法干净地拆分复用
模板引擎的思路是:视图(模板文件)和数据(Go 里的 struct/map)分离
模板文件(长什么样) + 数据(填什么) → 最终输出(HTML/文本)Go 标准库提供了两套模板引擎,语法几乎相同:
| 包 | 用途 |
|---|---|
text/template | 纯文本:邮件正文、配置、代码生成等 |
html/template | HTML:自动按上下文转义,降低 XSS 风险 |
做网页时,优先用html/template
2. 模板长什么样?
最简单的 HTML 模板:
<!-- hello.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>问候</title> </head> <body> <h1>Hello, {{.Name}}!</h1> <p>你今年 {{.Age}} 岁。</p> </body> </html>关键点是双花括号{{ }}:
{{.Name}}:输出当前数据的Name字段- 这里的
.叫当前上下文(dot),一开始等于你传入Execute的那份数据
模板里普通文字原样输出;只有{{ }}里的内容由引擎计算
3. 三个核心步骤:Parse → 准备数据 → Execute
用一句话概括标准库用法:
- Parse:把模板文本解析成内存中的模板对象
- 准备数据:构造 struct / map / 基本类型
- Execute:把数据灌进模板,写出结果
3.1 完整最小示例(输出到终端)
package main import ( "html/template" "os" ) type User struct { Name string Age int } func main() { const tpl = `Hello, {{.Name}}! Age={{.Age}}` t, err := template.New("hello").Parse(tpl) if err != nil { panic(err) } u := User{Name: "七米", Age: 18} if err := t.Execute(os.Stdout, u); err != nil { panic(err) } }运行结果:
Hello, 七米! Age=18逐步说明:
template.New("hello"):创建一个名为hello的模板.Parse(tpl):解析字符串模板Execute(os.Stdout, u):以u为根数据.,结果写到标准输出
3.2 从文件解析(更贴近真实项目)
package main import ( "html/template" "net/http" ) type User struct { Name string Age int } func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { tmpl, err := template.ParseFiles("hello.html") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } data := User{Name: "七米", Age: 18} if err := tmpl.Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) http.ListenAndServe(":8080", nil) }浏览器访问http://localhost:8080,就会看到渲染后的页面
说明:
ParseFiles:从磁盘读模板Execute(w, data):w是http.ResponseWriter,实现了io.Writer,所以可以直接当输出目标- 生产环境通常不会每次请求都
ParseFiles(后面会讲缓存/Must/一次加载),入门先求正确
4.template.Must:启动期失败就直接崩
解析模板失败时,很多程序选择「进程直接退出」,避免带着坏模板跑服务:
tmpl := template.Must(template.ParseFiles("hello.html"))Must的逻辑等价于:
t, err := template.ParseFiles("hello.html") if err != nil { panic(err) }适合在main或包初始化时加载模板;不适合在每个请求里乱用(panic 会打挂 goroutine/进程)。
5. 数据可以是什么?
模板的根数据常见有三类。
5.1 结构体(最常用)
type PageData struct { Title string User User }模板:
<title>{{.Title}}</title> <p>{{.User.Name}}</p>字段必须导出(首字母大写),否则模板访问不到。
5.2 map
data := map[string]any{ "Title": "首页", "Name": "七米", }模板:
<title>{{.Title}}</title> <p>{{.Name}}</p>Gin 里的gin.H本质上就是map[string]any,后面 lesson09 会用到。
5.3 基本类型
tmpl.Execute(w, "世界")模板:
Hello, {{.}}!此时.本身就是字符串"世界"。
6.text/templatevshtml/template
语法几乎一样,差别在输出是否做 HTML 安全处理。
对比实验
假设用户名是恶意输入:
name := `<script>alert("xss")</script>`用text/template
会原样输出脚本标签,浏览器可能执行 JS(危险)。
用html/template
会转义成类似:
<script>alert("xss")</script>页面上显示的是文本,而不是可执行脚本。
怎么选?
| 场景 | 选择 |
|---|---|
| 返回 HTML 页面 | html/template |
| 生成纯文本邮件、SQL 片段以外的文本、代码 | text/template |
| 不确定 | Web 相关一律html |
七米课里讲 Web,后续基本围绕html/template。
7.Parse家族 API 速览
入门阶段先熟悉这几个:
// 1) 字符串 template.New("name").Parse(`Hello {{.Name}}`) // 2) 一个或多个文件 template.ParseFiles("a.html", "b.html") // 3) 按 glob 匹配 template.ParseGlob("templates/*.html")注意:
ParseFiles/ParseGlob返回的是一个模板集合(*template.Template)- 集合里可以有多个命名模板
- 单文件时,
Execute通常就能工作;多文件时更推荐ExecuteTemplate(lesson06 会细讲)
Execute和ExecuteTemplate
tmpl.Execute(w, data) // 执行「默认/主」模板 tmpl.ExecuteTemplate(w, "home", data) // 按名字执行集合中的某个模板初学可以先记住:
- 只有一个简单文件:
Execute够用 - 一旦出现
{{define "xxx"}}或多个页面:用ExecuteTemplate
8. 一个稍完整的入门例子
目录:
lesson04-demo/ main.go hello.htmlhello.html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>{{.Title}}</title> <style> body { font-family: sans-serif; max-width: 640px; margin: 40px auto; } .card { border: 1px solid #ddd; padding: 16px; border-radius: 8px; } </style> </head> <body> <h1>{{.Title}}</h1> <div class="card"> <p>用户:{{.User.Name}}</p> <p>年龄:{{.User.Age}}</p> </div> </body> </html>main.go:
package main import ( "html/template" "log" "net/http" ) type User struct { Name string Age int } type ViewData struct { Title string User User } func main() { tmpl := template.Must(template.ParseFiles("hello.html")) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { data := ViewData{ Title: "模板初识 Demo", User: User{Name: "七米", Age: 18}, } if err := tmpl.Execute(w, data); err != nil { log.Println("execute:", err) } }) log.Println("listening on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }运行:
go run .打开浏览器看效果。把User.Name改成带<script>的字符串,观察html/template的转义行为——这是初识阶段很值得做的小实验。
9. 常见新手坑(lesson04 就会碰到)
坑 1:字段不大写
type User struct { name string // 错:模板访问不到 }模板{{.name}}会失败或为空(严格模式下报错)。应写成Name。
坑 2:文件路径不对
ParseFiles("hello.html")相对的是进程工作目录,不是源码文件所在目录。
在别的目录执行go run时容易找不到文件。
坑 3:忽略 error
Parse/Execute都可能失败。至少要打日志或返回 500,否则页面空白却不知道原因。
坑 4:用text/template渲 HTML
能跑,但不安全。Web 场景请用html/template。
坑 5:每次请求都 Parse(性能)
入门可以接受;真正服务应在启动时解析一次并复用*template.Template(它是并发安全可复用的)。
模板描述形状,数据提供内容;用
html/template解析后Execute出去。