在 Go 语言中,模板字符串
在 Go 语言中,模板字符串 主要通过标准库的 text/template(文本模板)和 html/template(HTML 安全模板)实现,核心用于动态拼接字符串、生成配置文件、HTML 页面等场景。Go 没有像 Python f-string 或 JavaScript 模板字符串那样的原生语法糖,而是通过模板引擎的模板语法和数据绑定实现动态内容渲染。
一、核心概念
- 模板(Template):包含固定文本和动态占位符(模板语法)的字符串/文件。
- 数据(Data):传入模板的变量(可以是基本类型、结构体、Map、切片等),用于填充占位符。
- 渲染(Execute):将数据注入模板,生成最终的字符串。
二、快速入门:text/template(通用文本模板)
text/template 适用于普通文本场景(如配置文件、日志、纯文本输出),语法简洁,无 HTML 转义。
1. 基本用法(字符串模板)
package mainimport ("os""text/template"
)func main() {// 1. 定义模板字符串({{.}} 是默认数据占位符)tplStr := "Hello, {{.Name}}! 你今年 {{.Age}} 岁,爱好是 {{.Hobbies}}。"// 2. 解析模板(检查语法合法性)tpl, err := template.New("test").Parse(tplStr)if err != nil {panic(err) // 实际项目中需优雅处理错误}// 3. 准备数据(支持结构体、Map、切片等)data := struct {Name stringAge intHobbies []string}{Name: "Go 开发者",Age: 3,Hobbies: []string{"编程", "开源", "云原生"},}// 4. 渲染模板(将数据注入,输出到 os.Stdout)err = tpl.Execute(os.Stdout, data)if err != nil {panic(err)}
}
输出结果:
Hello, Go 开发者! 你今年 3 岁,爱好是 [编程 开源 云原生]。
2. 关键模板语法
模板语法以 {{ }} 包裹,支持变量、循环、条件判断、函数调用等。
(1)变量访问
- 结构体字段:
{{.FieldName}}(字段必须导出,首字母大写)。 - Map 键:
{{.MapKey}}(Map 的键无需大写)。 - 切片/数组:
{{index .Slice 0}}(通过索引访问,索引从 0 开始)。
示例(Map 数据):
data := map[string]interface{}{"Name": "张三","Score": map[string]int{"Math": 95, "English": 88},"Tags": []string{"学生", "优秀"},
}tplStr := "姓名:{{.Name}}\n数学成绩:{{.Score.Math}}\n第一个标签:{{index .Tags 0}}"
// 输出:
// 姓名:张三
// 数学成绩:95
// 第一个标签:学生
(2)循环:{{range}}...{{end}}
用于遍历切片、数组、Map:
tplStr := `爱好列表:
{{range .Hobbies}}
- {{.}} // 循环内部 {{.}} 指代当前元素
{{end}}`// 输出:
// 爱好列表:
// - 编程
// - 开源
// - 云原生
带索引的循环(使用 $index, $elem 绑定索引和元素):
tplStr := `爱好列表(带索引):
{{range $i, $hobby := .Hobbies}}
{{$i+1}}. {{$hobby}} // $i 是索引,$hobby 是当前元素
{{end}}`
(3)条件判断:{{if}}...{{else if}}...{{else}}...{{end}}
支持布尔值、空值判断(0、""、nil、空切片/Map 均为 false):
tplStr := `{{.Name}} 的状态:
{{if gt .Age 18}} // gt 是 "大于" 函数(go 模板内置函数)
- 成年
{{else if eq .Age 18}} // eq 是 "等于"
- 刚成年
{{else}}
- 未成年
{{end}}`// 输出:Go 开发者 的状态:- 未成年
(4)内置函数
Go 模板内置了常用函数(如比较、字符串处理),调用格式:{{函数名 参数1 参数2 ...}}。
常用内置函数:
| 函数 | 功能 | 示例 |
|---|---|---|
eq | 等于(支持多参数) | {{eq .Age 3 5}} |
ne/lt/gt | 不等于/小于/大于 | {{gt .Score 90}} |
len | 长度(字符串/切片/Map) | {{len .Hobbies}} |
index | 索引访问 | {{index .Hobbies 0}} |
lower/upper | 小写/大写转换 | {{lower .Name}} |
printf | 格式化输出 | {{printf "%.2f" .Score}} |
(5)自定义函数
通过 Funcs 方法注册自定义函数,扩展模板能力:
// 定义自定义函数:计算切片长度的2倍
doubleLen := func(s []string) int {return len(s) * 2
}// 注册函数(需在 Parse 前调用)
tpl := template.New("test").Funcs(template.FuncMap{"doubleLen": doubleLen,
})// 解析模板
tpl.Parse(`爱好列表长度的2倍:{{doubleLen .Hobbies}}`)// 渲染
data := struct{ Hobbies []string }{{[]string{"a", "b"}}}
tpl.Execute(os.Stdout, data) // 输出:爱好列表长度的2倍:4
3. 文件模板(从文件读取模板)
实际项目中,模板常存储在文件中(如 config.tpl),通过 ParseFiles 读取:
步骤:
- 创建模板文件
config.tpl:
# 应用配置文件
app_name: {{.AppName}}
port: {{.Port}}
log_level: {{.LogLevel}}
allowed_ips:
{{range .AllowedIPs}}
- {{.}}
{{end}}
- Go 代码读取并渲染:
func main() {// 读取模板文件(支持多个文件,用逗号分隔)tpl, err := template.ParseFiles("config.tpl")if err != nil {panic(err)}// 数据data := map[string]interface{}{"AppName": "user-service","Port": 8080,"LogLevel": "info","AllowedIPs": []string{"127.0.0.1", "192.168.1.0/24"},}// 渲染到文件(也可输出到 os.Stdout)file, err := os.Create("config.yaml")if err != nil {panic(err)}defer file.Close()tpl.Execute(file, data)
}
- 生成的
config.yaml:
# 应用配置文件
app_name: user-service
port: 8080
log_level: info
allowed_ips:
- 127.0.0.1
- 192.168.1.0/24
三、html/template(HTML 安全模板)
html/template 是 text/template 的扩展,专为 HTML 输出设计,核心特性:自动转义危险字符(如 <、>、&),防止 XSS 攻击。
用法示例
package mainimport ("os""html/template"
)func main() {// 模板字符串(包含 HTML 标签和动态内容)tplStr := `<div><h1>{{.Title}}</h1><p>{{.Content}}</p> <!-- 危险内容会自动转义 --><p>{{.SafeContent}}</p> <!-- 信任的 HTML 内容需手动标记为安全 -->
</div>`// 解析模板tpl, err := template.New("htmlTest").Parse(tplStr)if err != nil {panic(err)}// 数据(包含危险脚本和信任的 HTML)data := struct {Title stringContent string // 危险内容(如脚本)SafeContent template.HTML // 信任的 HTML(需用 template.HTML 类型标记)}{Title: "HTML 模板测试",Content: "<script>alert('XSS 攻击')</script>", // 危险内容SafeContent: template.HTML("<strong>这是信任的加粗文本</strong>"), // 安全 HTML}// 渲染tpl.Execute(os.Stdout, data)
}
输出结果(自动转义危险字符):
<div><h1>HTML 模板测试</h1><p><script>alert('XSS 攻击')</script></p> <!-- 危险脚本被转义 --><p><strong>这是信任的加粗文本</strong></p> <!-- 信任的 HTML 正常渲染 -->
</div>
关键注意点
- 若需渲染信任的 HTML 内容,需将变量类型声明为
template.HTML(或template.CSS、template.JS等对应类型),否则会被转义。 - 禁止将用户输入直接标记为
template.HTML,仅用于自身控制的安全 HTML。
四、常见问题与最佳实践
1. 模板语法错误
- 占位符必须用
{{ }}包裹,且闭合(如{{if}}必须有{{end}})。 - 结构体字段必须导出(首字母大写),否则模板无法访问。
2. 性能优化
- 模板解析(
Parse/ParseFiles)是耗时操作,建议在程序启动时解析一次,全局复用模板对象。 - 避免频繁创建模板,优先使用
template.Must简化错误处理(适用于启动时解析):// 启动时解析模板(错误直接 panic,适合初始化场景) var tpl = template.Must(template.ParseFiles("config.tpl"))
3. 复杂模板组织
- 多模板文件可使用
ParseGlob批量读取(如ParseGlob("templates/*.tpl"))。 - 模板继承/包含:使用
{{template "子模板名" 数据}}包含其他模板,{{define "子模板名"}}...{{end}}定义子模板。
示例(模板包含):
// 主模板
tplStr := `
{{template "header" .}} <!-- 包含 header 子模板,传入当前数据 -->
<main>{{.Content}}</main>
{{template "footer"}} <!-- 包含 footer 子模板,无数据 -->
`// 定义子模板
tplStr += `
{{define "header"}}
<header><h1>{{.Title}}</h1></header>
{{end}}{{define "footer"}}
<footer>© 2025 Go 模板示例</footer>
{{end}}
`// 渲染后会包含 header、main、footer 内容
五、总结
| 模板库 | 适用场景 | 核心特性 |
|---|---|---|
text/template | 普通文本(配置、日志) | 简洁语法,无自动转义 |
html/template | HTML 页面输出 | 自动 XSS 防护,HTML 转义 |
Go 模板的核心是模板语法 + 数据绑定,通过 {{ }} 语法实现动态逻辑,配合结构体/Map 注入数据,适合各类动态文本生成场景。实际开发中,优先使用 html/template 处理 HTML 输出,避免 XSS 风险;普通文本场景使用 text/template 更简洁高效。
