🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
✨那些你一个人走过的夜路,终将化作照亮未 来的光



目标:搞清楚模板是什么、标准库怎么解析与执行、
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/template vs html/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.html
hello.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出去。