Go 模板初识

📅 2026/8/3 19:26:29 👁️ 阅读次数 📝 编程学习
Go 模板初识

🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
❄️个人专栏:数据结构与算法,数据库,leetcode
那些你一个人走过的夜路,终将化作照亮未来的光

目标:搞清楚模板是什么、标准库怎么解析与执行、text/templatehtml/template的区别,并能写出第一个可运行的渲染示例。


1. 为什么需要模板?

做 Web 或生成报告时,经常要把动态数据填进一段相对固定的文本里。

如果不使用模板,很容易写成这样:

html := "<h1>Hello, " + user.Name + "</h1>" html += "<p>年龄:" + strconv.Itoa(user.Age) + "</p>"

问题很明显:

  1. 难维护:HTML 结构和 Go 代码缠在一起
  2. 易出错:引号、转义、换行一多就乱
  3. 不安全:用户输入直接拼进 HTML,可能造成 XSS
  4. 难复用:页头页脚无法干净地拆分复用

模板引擎的思路是:视图(模板文件)和数据(Go 里的 struct/map)分离

模板文件(长什么样) + 数据(填什么) → 最终输出(HTML/文本)

Go 标准库提供了两套模板引擎,语法几乎相同:

用途
text/template纯文本:邮件正文、配置、代码生成等
html/templateHTML:自动按上下文转义,降低 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

用一句话概括标准库用法:

  1. Parse:把模板文本解析成内存中的模板对象
  2. 准备数据:构造 struct / map / 基本类型
  3. 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)whttp.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

会转义成类似:

&lt;script&gt;alert(&#34;xss&#34;)&lt;/script&gt;

页面上显示的是文本,而不是可执行脚本。

怎么选?

场景选择
返回 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 会细讲)

ExecuteExecuteTemplate

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出去。