Go Web 编程快速入门 07 - 模板(1):语法与最佳实践
在现代Web开发中,模板引擎是分离业务逻辑与视图展示的重要工具。Go语言内置的text/template和html/template包提供了强大而安全的模板功能。本章将深入探讨Go模板的语法基础和最佳实践,帮助你构建高效、安全的Web应用。
1. Go模板系统概述
1.1 模板包选择
Go提供了两个模板包:
package mainimport ("text/template" // 通用文本模板"html/template" // HTML模板,具有自动转义功能"fmt""os""strings"
)// TemplateManager 模板管理器
type TemplateManager struct {textTemplates map[string]*text.Template // 文本模板集合htmlTemplates map[string]*html.template.Template // HTML模板集合
}// NewTemplateManager 创建模板管理器
func NewTemplateManager() *TemplateManager {return &TemplateManager{textTemplates: make(map[string]*text.Template),htmlTemplates: make(map[string]*html.template.Template),}
}// AddTextTemplate 添加文本模板
func (tm *TemplateManager) AddTextTemplate(name, content string) error {tmpl, err := text.template.New(name).Parse(content)if err != nil {return fmt.Errorf("解析文本模板失败: %w", err)}tm.textTemplates[name] = tmplreturn nil
}// AddHTMLTemplate 添加HTML模板
func (tm *TemplateManager) AddHTMLTemplate(name, content string) error {tmpl, err := html.template.New(name).Parse(content)if err != nil {return fmt.Errorf("解析HTML模板失败: %w", err)}tm.htmlTemplates[name] = tmplreturn nil
}// ExecuteText 执行文本模板
func (tm *TemplateManager) ExecuteText(name string, data interface{}) (string, error) {tmpl, exists := tm.textTemplates[name]if !exists {return "", fmt.Errorf("文本模板 %s 不存在", name)}var buf strings.Builderif err := tmpl.Execute(&buf, data); err != nil {return "", fmt.Errorf("执行文本模板失败: %w", err)}return buf.String(), nil
}// ExecuteHTML 执行HTML模板
func (tm *TemplateManager) ExecuteHTML(name string, data interface{}) (string, error) {tmpl, exists := tm.htmlTemplates[name]if !exists {return "", fmt.Errorf("HTML模板 %s 不存在", name)}var buf strings.Builderif err := tmpl.Execute(&buf, data); err != nil {return "", fmt.Errorf("执行HTML模板失败: %w", err)}return buf.String(), nil
}
1.2 模板安全性对比
// SecurityDemo 安全性演示
type SecurityDemo struct {manager *TemplateManager
}// NewSecurityDemo 创建安全性演示
func NewSecurityDemo() *SecurityDemo {return &SecurityDemo{manager: NewTemplateManager(),}
}// DemonstrateEscaping 演示转义功能
func (sd *SecurityDemo) DemonstrateEscaping() {// 用户输入包含潜在的XSS攻击userInput := "<script>alert('XSS攻击!');</script>"data := map[string]interface{}{"UserInput": userInput,"SafeHTML": "<b>安全的HTML</b>",}// 文本模板 - 不进行转义textTemplate := `用户输入: {{.UserInput}}`sd.manager.AddTextTemplate("unsafe", textTemplate)// HTML模板 - 自动转义htmlTemplate := `<div>用户输入: {{.UserInput}}</div>
<div>安全HTML: {{.SafeHTML}}</div>`sd.manager.AddHTMLTemplate("safe", htmlTemplate)// 执行并比较结果textResult, _ := sd.manager.ExecuteText("unsafe", data)htmlResult, _ := sd.manager.ExecuteHTML("safe", data)fmt.Println("=== 安全性对比 ===")fmt.Println("文本模板结果(不安全):")fmt.Println(textResult)fmt.Println("\nHTML模板结果(安全):")fmt.Println(htmlResult)
}
2. 模板语法基础
2.1 变量与数据访问
// DataAccessDemo 数据访问演示
type DataAccessDemo struct {manager *TemplateManager
}// User 用户结构体
type User struct {ID int `json:"id"`Name string `json:"name"`Email string `json:"email"`Profile *UserProfile `json:"profile"`Tags []string `json:"tags"`Settings map[string]string `json:"settings"`
}// UserProfile 用户档案
type UserProfile struct {Avatar string `json:"avatar"`Bio string `json:"bio"`Location string `json:"location"`
}// NewDataAccessDemo 创建数据访问演示
func NewDataAccessDemo() *DataAccessDemo {return &DataAccessDemo{manager: NewTemplateManager(),}
}// SetupTemplates 设置模板
func (dad *DataAccessDemo) SetupTemplates() {// 基础变量访问模板basicTemplate := `
用户信息:
- ID: {{.ID}}
- 姓名: {{.Name}}
- 邮箱: {{.Email}}
`// 嵌套结构访问模板nestedTemplate := `
用户档案:
- 头像: {{.Profile.Avatar}}
- 简介: {{.Profile.Bio}}
- 位置: {{.Profile.Location}}
`// 数组访问模板arrayTemplate := `
用户标签:
{{range $index, $tag := .Tags}}
- [{{$index}}] {{$tag}}
{{end}}
`// Map访问模板mapTemplate := `
用户设置:
{{range $key, $value := .Settings}}
- {{$key}}: {{$value}}
{{end}}
`// 复合模板complexTemplate := `
=== 完整用户信息 ===
基本信息:ID: {{.ID}}姓名: {{.Name}}邮箱: {{.Email}}档案信息:
{{if .Profile}}头像: {{.Profile.Avatar}}简介: {{.Profile.Bio}}位置: {{.Profile.Location}}
{{else}}档案信息不完整
{{end}}标签 ({{len .Tags}} 个):
{{range $i, $tag := .Tags}}{{add $i 1}}. {{$tag}}
{{end}}设置:
{{range $key, $value := .Settings}}{{$key}} = {{$value}}
{{end}}
`dad.manager.AddTextTemplate("basic", basicTemplate)dad.manager.AddTextTemplate("nested", nestedTemplate)dad.manager.AddTextTemplate("array", arrayTemplate)dad.manager.AddTextTemplate("map", mapTemplate)dad.manager.AddTextTemplate("complex", complexTemplate)
}// DemonstrateDataAccess 演示数据访问
func (dad *DataAccessDemo) DemonstrateDataAccess() {// 创建测试数据user := &User{ID: 1001,Name: "张三",Email: "zhangsan@example.com",Profile: &UserProfile{Avatar: "/avatars/zhangsan.jpg",Bio: "Go语言爱好者,Web开发工程师",Location: "北京",},Tags: []string{"Go", "Web开发", "微服务", "云原生"},Settings: map[string]string{"theme": "dark","language": "zh-CN","timezone": "Asia/Shanghai",},}dad.SetupTemplates()// 演示各种数据访问templates := []string{"basic", "nested", "array", "map", "complex"}for _, tmplName := range templates {fmt.Printf("\n=== %s 模板结果 ===\n", tmplName)result, err := dad.manager.ExecuteText(tmplName, user)if err != nil {fmt.Printf("执行模板失败: %v\n", err)continue}fmt.Println(result)}
}
2.2 条件控制语句
// ConditionalDemo 条件控制演示
type ConditionalDemo struct {manager *TemplateManager
}// Product 产品结构体
type Product struct {ID int `json:"id"`Name string `json:"name"`Price float64 `json:"price"`Stock int `json:"stock"`Category string `json:"category"`IsActive bool `json:"is_active"`Description string `json:"description"`Tags []string `json:"tags"`
}// NewConditionalDemo 创建条件控制演示
func NewConditionalDemo() *ConditionalDemo {return &ConditionalDemo{manager: NewTemplateManager(),}
}// SetupConditionalTemplates 设置条件模板
func (cd *ConditionalDemo) SetupConditionalTemplates() {// 基础if条件模板basicIfTemplate := `
产品: {{.Name}}
{{if .IsActive}}
✅ 产品已激活
{{else}}
❌ 产品未激活
{{end}}
`// 复杂条件判断模板complexIfTemplate := `
产品状态检查: {{.Name}}
{{if .IsActive}}{{if gt .Stock 0}}{{if lt .Stock 10}}
✅ 库存紧张 (剩余: {{.Stock}}){{else}}
✅ 库存充足 (剩余: {{.Stock}}){{end}}{{else}}
❌ 库存不足{{end}}
{{else}}
❌ 产品未激活
{{end}}
`// 价格区间判断模板priceRangeTemplate := `
产品: {{.Name}}
价格: ¥{{printf "%.2f" .Price}}
{{if lt .Price 100}}
💰 经济实惠
{{else if lt .Price 500}}
💎 中等价位
{{else if lt .Price 1000}}
👑 高端产品
{{else}}
💸 奢侈品
{{end}}
`// 多条件组合模板multiConditionTemplate := `
=== 产品评估报告 ===
产品: {{.Name}} (ID: {{.ID}})状态评估:
{{if and .IsActive (gt .Stock 0)}}
✅ 可销售状态
{{else if .IsActive}}
⚠️ 已激活但库存不足
{{else}}
❌ 产品未激活
{{end}}价格策略:
{{if or (eq .Category "electronics") (eq .Category "books")}}{{if lt .Price 200}}
🎯 推荐促销{{else}}
💼 正常销售{{end}}
{{else}}
🏷️ 其他类别产品
{{end}}库存警告:
{{if le .Stock 5}}
🚨 库存严重不足!
{{else if le .Stock 20}}
⚠️ 库存偏低
{{else}}
✅ 库存正常
{{end}}
`// 字符串条件模板stringConditionTemplate := `
产品分类分析: {{.Name}}
{{if eq .Category "electronics"}}
📱 电子产品 - 需要技术支持
{{else if eq .Category "books"}}
📚 图书产品 - 需要内容审核
{{else if eq .Category "clothing"}}
👕 服装产品 - 需要尺码管理
{{else}}
📦 其他类别产品
{{end}}描述检查:
{{if .Description}}{{if gt (len .Description) 100}}
📝 详细描述 ({{len .Description}} 字符){{else}}
📄 简短描述 ({{len .Description}} 字符){{end}}
{{else}}
❌ 缺少产品描述
{{end}}
`cd.manager.AddTextTemplate("basic_if", basicIfTemplate)cd.manager.AddTextTemplate("complex_if", complexIfTemplate)cd.manager.AddTextTemplate("price_range", priceRangeTemplate)cd.manager.AddTextTemplate("multi_condition", multiConditionTemplate)cd.manager.AddTextTemplate("string_condition", stringConditionTemplate)
}// DemonstrateConditionals 演示条件控制
func (cd *ConditionalDemo) DemonstrateConditionals() {// 创建不同状态的产品进行测试products := []*Product{{ID: 1,Name: "iPhone 15 Pro",Price: 7999.00,Stock: 3,Category: "electronics",IsActive: true,Description: "最新款iPhone,配备A17 Pro芯片,钛金属设计,支持USB-C接口。",Tags: []string{"手机", "苹果", "高端"},},{ID: 2,Name: "Go语言编程",Price: 89.00,Stock: 0,Category: "books",IsActive: true,Description: "深入学习Go语言的经典教程",Tags: []string{"编程", "Go", "教程"},},{ID: 3,Name: "休闲T恤",Price: 59.00,Stock: 150,Category: "clothing",IsActive: false,Description: "",Tags: []string{"服装", "休闲"},},}cd.SetupConditionalTemplates()templates := []string{"basic_if", "complex_if", "price_range", "multi_condition", "string_condition"}for _, product := range products {fmt.Printf("\n" + strings.Repeat("=", 50))fmt.Printf("\n测试产品: %s\n", product.Name)fmt.Println(strings.Repeat("=", 50))for _, tmplName := range templates {fmt.Printf("\n--- %s ---\n", tmplName)result, err := cd.manager.ExecuteText(tmplName, product)if err != nil {fmt.Printf("执行模板失败: %v\n", err)continue}fmt.Print(result)}}
}
2.3 循环控制语句
// LoopDemo 循环控制演示
type LoopDemo struct {manager *TemplateManager
}// BlogPost 博客文章
type BlogPost struct {ID int `json:"id"`Title string `json:"title"`Content string `json:"content"`Author string `json:"author"`Tags []string `json:"tags"`Comments []Comment `json:"comments"`Meta map[string]interface{} `json:"meta"`
}// Comment 评论
type Comment struct {ID int `json:"id"`Author string `json:"author"`Content string `json:"content"`Likes int `json:"likes"`
}// BlogData 博客数据
type BlogData struct {Posts []BlogPost `json:"posts"`Categories map[string][]BlogPost `json:"categories"`Stats map[string]int `json:"stats"`
}// NewLoopDemo 创建循环演示
func NewLoopDemo() *LoopDemo {return &LoopDemo{manager: NewTemplateManager(),}
}// SetupLoopTemplates 设置循环模板
func (ld *LoopDemo) SetupLoopTemplates() {// 基础range循环模板basicRangeTemplate := `
=== 文章列表 ===
{{range .Posts}}
📄 {{.Title}} (作者: {{.Author}})
{{end}}
`// 带索引的循环模板indexRangeTemplate := `
=== 编号文章列表 ===
{{range $index, $post := .Posts}}
{{add $index 1}}. {{$post.Title}}作者: {{$post.Author}}标签: {{join $post.Tags ", "}}
{{end}}
`// 嵌套循环模板nestedRangeTemplate := `
=== 文章详情 ===
{{range $postIndex, $post := .Posts}}📄 文章 {{add $postIndex 1}}: {{$post.Title}}作者: {{$post.Author}}🏷️ 标签:{{range $tagIndex, $tag := $post.Tags}}- [{{$tagIndex}}] {{$tag}}{{end}}💬 评论 ({{len $post.Comments}} 条):{{range $commentIndex, $comment := $post.Comments}}{{add $commentIndex 1}}. {{$comment.Author}}: {{$comment.Content}}👍 {{$comment.Likes}} 个赞{{end}}
{{end}}
`// Map循环模板mapRangeTemplate := `
=== 分类统计 ===
{{range $category, $posts := .Categories}}
📂 {{$category}} ({{len $posts}} 篇文章):
{{range $posts}}- {{.Title}}
{{end}}
{{end}}=== 网站统计 ===
{{range $key, $value := .Stats}}
📊 {{$key}}: {{$value}}
{{end}}
`// 条件循环模板conditionalRangeTemplate := `
=== 热门文章筛选 ===
{{range .Posts}}
{{if gt (len .Comments) 2}}
🔥 热门: {{.Title}}评论数: {{len .Comments}}{{range .Comments}}💬 {{.Author}}: {{.Content}}{{end}}
{{end}}
{{end}}=== 长标题文章 ===
{{range .Posts}}
{{if gt (len .Title) 10}}
📝 {{.Title}} ({{len .Title}} 字符)
{{end}}
{{end}}
`// 复杂循环控制模板complexRangeTemplate := `
=== 综合文章报告 ===
{{$totalPosts := len .Posts}}
{{$totalComments := 0}}
{{range .Posts}}
{{$totalComments = add $totalComments (len .Comments)}}
{{end}}📊 总体统计:
- 文章总数: {{$totalPosts}}
- 评论总数: {{$totalComments}}
- 平均评论数: {{if gt $totalPosts 0}}{{div $totalComments $totalPosts}}{{else}}0{{end}}📄 文章详细分析:
{{range $index, $post := .Posts}}
{{$commentCount := len $post.Comments}}
{{$isPopular := gt $commentCount 2}}[{{add $index 1}}] {{$post.Title}}作者: {{$post.Author}}评论: {{$commentCount}} 条状态: {{if $isPopular}}🔥 热门{{else}}📝 普通{{end}}标签: {{if $post.Tags}}{{join $post.Tags " | "}}{{else}}无标签{{end}}{{if $isPopular}}热门评论:{{range $post.Comments}}💬 {{.Author}}: {{.Content}} (👍{{.Likes}}){{end}}{{end}}
{{end}}
`ld.manager.AddTextTemplate("basic_range", basicRangeTemplate)ld.manager.AddTextTemplate("index_range", indexRangeTemplate)ld.manager.AddTextTemplate("nested_range", nestedRangeTemplate)ld.manager.AddTextTemplate("map_range", mapRangeTemplate)ld.manager.AddTextTemplate("conditional_range", conditionalRangeTemplate)ld.manager.AddTextTemplate("complex_range", complexRangeTemplate)
}// DemonstrateLoops 演示循环控制
func (ld *LoopDemo) DemonstrateLoops() {// 创建测试数据posts := []BlogPost{{ID: 1,Title: "Go语言Web开发入门",Content: "本文介绍Go语言Web开发的基础知识...",Author: "张三",Tags: []string{"Go", "Web", "入门"},Comments: []Comment{{ID: 1, Author: "李四", Content: "写得很好!", Likes: 5},{ID: 2, Author: "王五", Content: "学到了很多", Likes: 3},{ID: 3, Author: "赵六", Content: "期待更多内容", Likes: 2},},Meta: map[string]interface{}{"views": 1250,"published": true,},},{ID: 2,Title: "模板引擎最佳实践",Content: "深入探讨Go模板引擎的使用技巧...",Author: "李四",Tags: []string{"Go", "模板", "最佳实践"},Comments: []Comment{{ID: 4, Author: "张三", Content: "实用性很强", Likes: 8},},Meta: map[string]interface{}{"views": 890,"published": true,},},{ID: 3,Title: "微服务架构设计",Content: "介绍微服务架构的设计原则和实践...",Author: "王五",Tags: []string{"微服务", "架构", "设计"},Comments: []Comment{{ID: 5, Author: "张三", Content: "架构思路清晰", Likes: 6},{ID: 6, Author: "李四", Content: "很有启发", Likes: 4},{ID: 7, Author: "赵六", Content: "实践性强", Likes: 7},{ID: 8, Author: "钱七", Content: "收藏了", Likes: 2},},Meta: map[string]interface{}{"views": 2100,"published": true,},},}// 按分类组织文章categories := map[string][]BlogPost{"编程语言": {posts[0], posts[1]},"架构设计": {posts[2]},"Web开发": {posts[0]},}// 统计信息stats := map[string]int{"总文章数": len(posts),"总作者数": 3,"总评论数": 8,"总浏览数": 4240,}blogData := &BlogData{Posts: posts,Categories: categories,Stats: stats,}ld.SetupLoopTemplates()templates := []string{"basic_range", "index_range", "nested_range", "map_range", "conditional_range", "complex_range"}for _, tmplName := range templates {fmt.Printf("\n" + strings.Repeat("=", 60))fmt.Printf("\n%s 模板结果", tmplName)fmt.Printf("\n" + strings.Repeat("=", 60) + "\n")result, err := ld.manager.ExecuteText(tmplName, blogData)if err != nil {fmt.Printf("执行模板失败: %v\n", err)continue}fmt.Print(result)}
}
3. 模板函数与管道
3.1 内置函数使用
// BuiltinFunctionDemo 内置函数演示
type BuiltinFunctionDemo struct {manager *TemplateManager
}// NewBuiltinFunctionDemo 创建内置函数演示
func NewBuiltinFunctionDemo() *BuiltinFunctionDemo {return &BuiltinFunctionDemo{manager: NewTemplateManager(),}
}// SetupBuiltinFunctionTemplates 设置内置函数模板
func (bfd *BuiltinFunctionDemo) SetupBuiltinFunctionTemplates() {// 数学运算函数模板mathTemplate := `
=== 数学运算函数 ===
原始数值: {{.Number}}
加法: {{add .Number 10}} ({{.Number}} + 10)
减法: {{sub .Number 5}} ({{.Number}} - 5)
乘法: {{mul .Number 2}} ({{.Number}} * 2)
除法: {{div .Number 3}} ({{.Number}} / 3)
取模: {{mod .Number 7}} ({{.Number}} % 7)比较运算:
等于10: {{eq .Number 10}}
不等于10: {{ne .Number 10}}
大于5: {{gt .Number 5}}
大于等于10: {{ge .Number 10}}
小于20: {{lt .Number 20}}
小于等于15: {{le .Number 15}}
`// 逻辑运算函数模板logicTemplate := `
=== 逻辑运算函数 ===
布尔值1: {{.Bool1}}
布尔值2: {{.Bool2}}
AND运算: {{and .Bool1 .Bool2}}
OR运算: {{or .Bool1 .Bool2}}
NOT运算1: {{not .Bool1}}
NOT运算2: {{not .Bool2}}复合逻辑:
{{if and .Bool1 (not .Bool2)}}
✅ Bool1为真且Bool2为假
{{else}}
❌ 条件不满足
{{end}}
`// 字符串处理函数模板stringTemplate := `
=== 字符串处理函数 ===
原始字符串: "{{.Text}}"
长度: {{len .Text}}
打印格式化: {{printf "格式化: [%s]" .Text}}
HTML转义: {{html .Text}}
URL查询转义: {{urlquery .Text}}
JavaScript转义: {{js .Text}}数组处理:
标签数组: {{.Tags}}
数组长度: {{len .Tags}}
索引访问: {{index .Tags 0}} (第一个元素)
{{if gt (len .Tags) 2}}
第三个元素: {{index .Tags 2}}
{{end}}
`bfd.manager.AddTextTemplate("math", mathTemplate)bfd.manager.AddTextTemplate("logic", logicTemplate)bfd.manager.AddTextTemplate("string", stringTemplate)
}// DemonstrateBuiltinFunctions 演示内置函数
func (bfd *BuiltinFunctionDemo) DemonstrateBuiltinFunctions() {testData := map[string]interface{}{"Number": 15,"Bool1": true,"Bool2": false,"Text": "<script>alert('Hello & 世界');</script>","Tags": []string{"Go", "Web", "模板", "函数"},}bfd.SetupBuiltinFunctionTemplates()templates := []string{"math", "logic", "string"}for _, tmplName := range templates {fmt.Printf("\n" + strings.Repeat("-", 50))fmt.Printf("\n%s 模板结果", tmplName)fmt.Printf("\n" + strings.Repeat("-", 50) + "\n")result, err := bfd.manager.ExecuteText(tmplName, testData)if err != nil {fmt.Printf("执行模板失败: %v\n", err)continue}fmt.Print(result)}
}
3.2 自定义函数
// CustomFunctionDemo 自定义函数演示
type CustomFunctionDemo struct {textTemplates map[string]*text.TemplatehtmlTemplates map[string]*html.template.Template
}// NewCustomFunctionDemo 创建自定义函数演示
func NewCustomFunctionDemo() *CustomFunctionDemo {return &CustomFunctionDemo{textTemplates: make(map[string]*text.Template),htmlTemplates: make(map[string]*html.template.Template),}
}// GetCustomFuncMap 获取自定义函数映射
func (cfd *CustomFunctionDemo) GetCustomFuncMap() text.template.FuncMap {return text.template.FuncMap{// 字符串处理函数"upper": strings.ToUpper, // 转大写"lower": strings.ToLower, // 转小写"title": strings.Title, // 标题格式"trim": strings.TrimSpace, // 去除首尾空格"replace": strings.ReplaceAll, // 字符串替换"contains": strings.Contains, // 包含检查"hasPrefix": strings.HasPrefix, // 前缀检查"hasSuffix": strings.HasSuffix, // 后缀检查"split": strings.Split, // 字符串分割"join": strings.Join, // 字符串连接// 数学计算函数"abs": func(n int) int { // 绝对值if n < 0 {return -n}return n},"max": func(a, b int) int { // 最大值if a > b {return a}return b},"min": func(a, b int) int { // 最小值if a < b {return a}return b},"pow": func(base, exp int) int { // 幂运算result := 1for i := 0; i < exp; i++ {result *= base}return result},// 时间处理函数"now": func() string { // 当前时间return fmt.Sprintf("%v", "2024-01-15 10:30:00")},"formatTime": func(layout, timeStr string) string { // 时间格式化return fmt.Sprintf("格式化时间: %s", timeStr)},// 数组/切片处理函数"first": func(slice []interface{}) interface{} { // 第一个元素if len(slice) > 0 {return slice[0]}return nil},"last": func(slice []interface{}) interface{} { // 最后一个元素if len(slice) > 0 {return slice[len(slice)-1]}return nil},"reverse": func(slice []string) []string { // 反转数组result := make([]string, len(slice))for i, v := range slice {result[len(slice)-1-i] = v}return result},"unique": func(slice []string) []string { // 去重seen := make(map[string]bool)var result []stringfor _, v := range slice {if !seen[v] {seen[v] = trueresult = append(result, v)}}return result},// 条件判断函数"isEmpty": func(s string) bool { // 是否为空return strings.TrimSpace(s) == ""},"isNotEmpty": func(s string) bool { // 是否非空return strings.TrimSpace(s) != ""},"default": func(defaultVal, val interface{}) interface{} { // 默认值if val == nil || val == "" {return defaultVal}return val},// 格式化函数"currency": func(amount float64) string { // 货币格式化return fmt.Sprintf("¥%.2f", amount)},"percentage": func(val float64) string { // 百分比格式化return fmt.Sprintf("%.1f%%", val*100)},"fileSize": func(bytes int64) string { // 文件大小格式化const unit = 1024if bytes < unit {return fmt.Sprintf("%d B", bytes)}div, exp := int64(unit), 0for n := bytes / unit; n >= unit; n /= unit {div *= unitexp++}return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])},// 安全函数"safeHTML": func(s string) html.template.HTML { // 安全HTMLreturn html.template.HTML(s)},"safeCSS": func(s string) html.template.CSS { // 安全CSSreturn html.template.CSS(s)},"safeJS": func(s string) html.template.JS { // 安全JavaScriptreturn html.template.JS(s)},}
}// AddTextTemplateWithFuncs 添加带自定义函数的文本模板
func (cfd *CustomFunctionDemo) AddTextTemplateWithFuncs(name, content string) error {tmpl := text.template.New(name).Funcs(cfd.GetCustomFuncMap())var err errortmpl, err = tmpl.Parse(content)if err != nil {return fmt.Errorf("解析模板失败: %w", err)}cfd.textTemplates[name] = tmplreturn nil
}// ExecuteTextTemplate 执行文本模板
func (cfd *CustomFunctionDemo) ExecuteTextTemplate(name string, data interface{}) (string, error) {tmpl, exists := cfd.textTemplates[name]if !exists {return "", fmt.Errorf("模板 %s 不存在", name)}var buf strings.Builderif err := tmpl.Execute(&buf, data); err != nil {return "", fmt.Errorf("执行模板失败: %w", err)}return buf.String(), nil
}// SetupCustomFunctionTemplates 设置自定义函数模板
func (cfd *CustomFunctionDemo) SetupCustomFunctionTemplates() {// 字符串处理模板stringFuncTemplate := `
=== 字符串处理函数 ===
原始文本: "{{.Text}}"
大写: {{upper .Text}}
小写: {{lower .Text}}
标题格式: {{title .Text}}
去空格: "{{trim .SpacedText}}"
替换: {{replace .Text "Go" "Golang"}}
包含"Web": {{contains .Text "Web"}}
以"Go"开头: {{hasPrefix .Text "Go"}}
以"开发"结尾: {{hasSuffix .Text "开发"}}分割与连接:
分割结果: {{split .Text " "}}
连接标签: {{join .Tags " | "}}
`// 数学计算模板mathFuncTemplate := `
=== 数学计算函数 ===
数值A: {{.NumA}}, 数值B: {{.NumB}}
绝对值A: {{abs .NumA}}
最大值: {{max .NumA .NumB}}
最小值: {{min .NumA .NumB}}
A的3次方: {{pow .NumA 3}}
B的2次方: {{pow .NumB 2}}
`// 数组处理模板arrayFuncTemplate := `
=== 数组处理函数 ===
原始数组: {{.Items}}
第一个元素: {{first .Items}}
最后一个元素: {{last .Items}}
反转数组: {{reverse .StringItems}}
去重数组: {{unique .DuplicateItems}}
数组长度: {{len .Items}}
`// 格式化函数模板formatFuncTemplate := `
=== 格式化函数 ===
价格: {{currency .Price}}
折扣率: {{percentage .Discount}}
文件大小: {{fileSize .FileSize}}
当前时间: {{now}}
格式化时间: {{formatTime "2006-01-02" "2024-01-15"}}条件判断:
空字符串检查: {{isEmpty .EmptyText}}
非空字符串检查: {{isNotEmpty .Text}}
默认值处理: {{default "默认标题" .EmptyTitle}}
`// 复合函数模板complexFuncTemplate := `
=== 复合函数应用 ===
{{$title := default "无标题" .Title}}
{{$cleanTitle := trim $title}}
{{$upperTitle := upper $cleanTitle}}处理后标题: {{$upperTitle}}价格信息:
{{range .Products}}
- {{.Name}}: {{currency .Price}}{{if gt .Discount 0}}折扣: {{percentage .Discount}}{{end}}文件大小: {{fileSize .Size}}
{{end}}标签处理:
{{$uniqueTags := unique .AllTags}}
{{$reversedTags := reverse $uniqueTags}}
去重后标签: {{join $uniqueTags " | "}}
反转后标签: {{join $reversedTags " | "}}数学计算:
{{range .Numbers}}
数值: {{.}}, 绝对值: {{abs .}}, 平方: {{pow . 2}}
{{end}}
`cfd.AddTextTemplateWithFuncs("string_func", stringFuncTemplate)cfd.AddTextTemplateWithFuncs("math_func", mathFuncTemplate)cfd.AddTextTemplateWithFuncs("array_func", arrayFuncTemplate)cfd.AddTextTemplateWithFuncs("format_func", formatFuncTemplate)cfd.AddTextTemplateWithFuncs("complex_func", complexFuncTemplate)
}// DemonstrateCustomFunctions 演示自定义函数
func (cfd *CustomFunctionDemo) DemonstrateCustomFunctions() {// 创建测试数据testData := map[string]interface{}{"Text": "Go Web 开发","SpacedText": " 带空格的文本 ","EmptyText": "","EmptyTitle": "","Title": " Go语言教程 ","NumA": -15,"NumB": 8,"Tags": []string{"Go", "Web", "开发", "教程"},"Items": []interface{}{"第一个", "第二个", "第三个"},"StringItems": []string{"Apple", "Banana", "Cherry"},"DuplicateItems": []string{"Go", "Web", "Go", "开发", "Web", "教程"},"Price": 299.99,"Discount": 0.15,"FileSize": int64(1048576), // 1MB"Products": []map[string]interface{}{{"Name": "Go语言书籍", "Price": 89.00, "Discount": 0.1, "Size": int64(2048)},{"Name": "Web开发工具", "Price": 199.00, "Discount": 0.0, "Size": int64(5242880)},},"AllTags": []string{"Go", "Web", "开发", "Go", "教程", "Web", "实践"},"Numbers": []int{-5, 3, -8, 12, 0},}cfd.SetupCustomFunctionTemplates()templates := []string{"string_func", "math_func", "array_func", "format_func", "complex_func"}for _, tmplName := range templates {fmt.Printf("\n" + strings.Repeat("=", 60))fmt.Printf("\n%s 模板结果", tmplName)fmt.Printf("\n" + strings.Repeat("=", 60) + "\n")result, err := cfd.ExecuteTextTemplate(tmplName, testData)if err != nil {fmt.Printf("执行模板失败: %v\n", err)continue}fmt.Print(result)}
}
4. 综合实践项目:博客系统模板
// BlogTemplateSystem 博客模板系统
type BlogTemplateSystem struct {textTemplates map[string]*text.TemplatehtmlTemplates map[string]*html.template.TemplatefuncMap text.template.FuncMap
}// Article 文章结构
type Article struct {ID int `json:"id"`Title string `json:"title"`Content string `json:"content"`Summary string `json:"summary"`Author Author `json:"author"`Category Category `json:"category"`Tags []Tag `json:"tags"`Comments []Comment `json:"comments"`PublishedAt string `json:"published_at"`UpdatedAt string `json:"updated_at"`ViewCount int `json:"view_count"`LikeCount int `json:"like_count"`Status string `json:"status"` // published, draft, archived
}// Author 作者结构
type Author struct {ID int `json:"id"`Name string `json:"name"`Email string `json:"email"`Avatar string `json:"avatar"`Bio string `json:"bio"`
}// Category 分类结构
type Category struct {ID int `json:"id"`Name string `json:"name"`Description string `json:"description"`Color string `json:"color"`
}// Tag 标签结构
type Tag struct {ID int `json:"id"`Name string `json:"name"`Color string `json:"color"`
}// BlogSite 博客站点信息
type BlogSite struct {Title string `json:"title"`Description string `json:"description"`Articles []Article `json:"articles"`Categories []Category `json:"categories"`Tags []Tag `json:"tags"`Authors []Author `json:"authors"`Stats map[string]int `json:"stats"`Config map[string]string `json:"config"`
}// NewBlogTemplateSystem 创建博客模板系统
func NewBlogTemplateSystem() *BlogTemplateSystem {bts := &BlogTemplateSystem{textTemplates: make(map[string]*text.Template),htmlTemplates: make(map[string]*html.template.Template),}// 设置自定义函数bts.funcMap = text.template.FuncMap{// 字符串处理"truncate": func(s string, length int) string {if len(s) <= length {return s}return s[:length] + "..."},"slug": func(s string) string {return strings.ToLower(strings.ReplaceAll(s, " ", "-"))},// 时间处理"timeAgo": func(timeStr string) string {return timeStr + " (3天前)" // 简化实现},"formatDate": func(timeStr string) string {return "2024年1月15日" // 简化实现},// 数组处理"slice": func(items []Article, start, end int) []Article {if start < 0 || start >= len(items) {return []Article{}}if end > len(items) {end = len(items)}return items[start:end]},// 统计函数"countByStatus": func(articles []Article, status string) int {count := 0for _, article := range articles {if article.Status == status {count++}}return count},"countByCategory": func(articles []Article, categoryID int) int {count := 0for _, article := range articles {if article.Category.ID == categoryID {count++}}return count},// 过滤函数"filterByStatus": func(articles []Article, status string) []Article {var filtered []Articlefor _, article := range articles {if article.Status == status {filtered = append(filtered, article)}}return filtered},"filterByCategory": func(articles []Article, categoryID int) []Article {var filtered []Articlefor _, article := range articles {if article.Category.ID == categoryID {filtered = append(filtered, article)}}return filtered},// 排序函数"sortByViews": func(articles []Article) []Article {// 简化实现,实际应该进行排序return articles},"sortByDate": func(articles []Article) []Article {// 简化实现,实际应该进行排序return articles},// 格式化函数"formatNumber": func(n int) string {if n >= 1000000 {return fmt.Sprintf("%.1fM", float64(n)/1000000)} else if n >= 1000 {return fmt.Sprintf("%.1fK", float64(n)/1000)}return fmt.Sprintf("%d", n)},// 安全函数"safeHTML": func(s string) html.template.HTML {return html.template.HTML(s)},}return bts
}// AddTemplate 添加模板
func (bts *BlogTemplateSystem) AddTemplate(name, content string, isHTML bool) error {if isHTML {tmpl := html.template.New(name).Funcs(html.template.FuncMap(bts.funcMap))var err errortmpl, err = tmpl.Parse(content)if err != nil {return fmt.Errorf("解析HTML模板失败: %w", err)}bts.htmlTemplates[name] = tmpl} else {tmpl := text.template.New(name).Funcs(bts.funcMap)var err errortmpl, err = tmpl.Parse(content)if err != nil {return fmt.Errorf("解析文本模板失败: %w", err)}bts.textTemplates[name] = tmpl}return nil
}// ExecuteTemplate 执行模板
func (bts *BlogTemplateSystem) ExecuteTemplate(name string, data interface{}, isHTML bool) (string, error) {var buf strings.Builderif isHTML {tmpl, exists := bts.htmlTemplates[name]if !exists {return "", fmt.Errorf("HTML模板 %s 不存在", name)}if err := tmpl.Execute(&buf, data); err != nil {return "", fmt.Errorf("执行HTML模板失败: %w", err)}} else {tmpl, exists := bts.textTemplates[name]if !exists {return "", fmt.Errorf("文本模板 %s 不存在", name)}if err := tmpl.Execute(&buf, data); err != nil {return "", fmt.Errorf("执行文本模板失败: %w", err)}}return buf.String(), nil
}// SetupBlogTemplates 设置博客模板
func (bts *BlogTemplateSystem) SetupBlogTemplates() {// 文章列表模板articleListTemplate := `
=== {{.Title}} ===
{{.Description}}📊 站点统计:
{{range $key, $value := .Stats}}
- {{$key}}: {{formatNumber $value}}
{{end}}📂 分类列表:
{{range .Categories}}
- {{.Name}} ({{countByCategory $.Articles .ID}} 篇文章){{.Description}}
{{end}}📄 最新文章 ({{len .Articles}} 篇):
{{range $index, $article := .Articles}}
{{add $index 1}}. {{$article.Title}}作者: {{$article.Author.Name}}分类: {{$article.Category.Name}}发布: {{formatDate $article.PublishedAt}}浏览: {{formatNumber $article.ViewCount}} | 点赞: {{formatNumber $article.LikeCount}}摘要: {{truncate $article.Summary 100}}标签: {{range $article.Tags}}#{{.Name}} {{end}}状态: {{if eq $article.Status "published"}}✅ 已发布{{else if eq $article.Status "draft"}}📝 草稿{{else}}📦 已归档{{end}}
{{end}}
`// 文章详情模板articleDetailTemplate := `
=== 文章详情 ===
标题: {{.Title}}
作者: {{.Author.Name}} ({{.Author.Email}})
分类: {{.Category.Name}}
发布时间: {{formatDate .PublishedAt}}
更新时间: {{timeAgo .UpdatedAt}}
浏览量: {{formatNumber .ViewCount}}
点赞数: {{formatNumber .LikeCount}}标签:
{{range .Tags}}
🏷️ {{.Name}}
{{end}}内容:
{{.Content}}💬 评论 ({{len .Comments}} 条):
{{range $index, $comment := .Comments}}
{{add $index 1}}. {{$comment.Author}} ({{$comment.Likes}} 👍){{$comment.Content}}
{{end}}
`// 分类统计模板categoryStatsTemplate := `
=== 分类统计报告 ===
{{range .Categories}}
📂 {{.Name}}描述: {{.Description}}颜色: {{.Color}}文章数: {{countByCategory $.Articles .ID}}文章列表:{{range filterByCategory $.Articles .ID}}- {{.Title}} ({{formatNumber .ViewCount}} 浏览){{end}}
{{end}}📊 状态统计:
- 已发布: {{countByStatus .Articles "published"}} 篇
- 草稿: {{countByStatus .Articles "draft"}} 篇
- 已归档: {{countByStatus .Articles "archived"}} 篇
`// 作者信息模板authorProfileTemplate := `
=== 作者档案 ===
{{range .Authors}}
👤 {{.Name}}邮箱: {{.Email}}头像: {{.Avatar}}简介: {{.Bio}}发表文章:{{range $.Articles}}{{if eq .Author.ID $.ID}}- {{.Title}} ({{formatDate .PublishedAt}}){{end}}{{end}}
{{end}}
`// HTML文章卡片模板articleCardHTMLTemplate := `
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title><style>.article-card { border: 1px solid #ddd; margin: 10px; padding: 15px; border-radius: 5px; }.article-title { font-size: 1.5em; font-weight: bold; color: #333; }.article-meta { color: #666; font-size: 0.9em; margin: 5px 0; }.article-summary { margin: 10px 0; line-height: 1.6; }.article-tags { margin: 10px 0; }.tag { background: #e1f5fe; padding: 2px 8px; border-radius: 3px; margin-right: 5px; }.stats { color: #888; font-size: 0.8em; }</style>
</head>
<body><h1>{{.Title}}</h1><p>{{.Description}}</p>{{range .Articles}}<div class="article-card"><div class="article-title">{{.Title}}</div><div class="article-meta">作者: {{.Author.Name}} | 分类: {{.Category.Name}} | 发布: {{formatDate .PublishedAt}}</div><div class="article-summary">{{.Summary}}</div><div class="article-tags">{{range .Tags}}<span class="tag">{{.Name}}</span>{{end}}</div><div class="stats">👁️ {{formatNumber .ViewCount}} | 👍 {{formatNumber .LikeCount}} | 💬 {{len .Comments}}</div></div>{{end}}
</body>
</html>
`// 添加模板bts.AddTemplate("article_list", articleListTemplate, false)bts.AddTemplate("article_detail", articleDetailTemplate, false)bts.AddTemplate("category_stats", categoryStatsTemplate, false)bts.AddTemplate("author_profile", authorProfileTemplate, false)bts.AddTemplate("article_cards_html", articleCardHTMLTemplate, true)
}// DemonstrateBlogSystem 演示博客系统
func (bts *BlogTemplateSystem) DemonstrateBlogSystem() {// 创建测试数据authors := []Author{{ID: 1, Name: "张三", Email: "zhangsan@example.com", Avatar: "/avatars/zhangsan.jpg", Bio: "Go语言专家,Web开发爱好者"},{ID: 2, Name: "李四", Email: "lisi@example.com", Avatar: "/avatars/lisi.jpg", Bio: "全栈开发工程师,技术写作者"},}categories := []Category{{ID: 1, Name: "Go语言", Description: "Go语言相关技术文章", Color: "#00ADD8"},{ID: 2, Name: "Web开发", Description: "Web开发技术与实践", Color: "#FF6B6B"},{ID: 3, Name: "架构设计", Description: "软件架构设计与模式", Color: "#4ECDC4"},}tags := []Tag{{ID: 1, Name: "Go", Color: "#00ADD8"},{ID: 2, Name: "Web", Color: "#FF6B6B"},{ID: 3, Name: "模板", Color: "#4ECDC4"},{ID: 4, Name: "最佳实践", Color: "#45B7D1"},}comments := []Comment{{ID: 1, Author: "读者A", Content: "写得很好,学到了很多!", Likes: 15},{ID: 2, Author: "读者B", Content: "期待更多关于模板的内容", Likes: 8},{ID: 3, Author: "读者C", Content: "实用性很强,收藏了", Likes: 12},}articles := []Article{{ID: 1,Title: "Go模板引擎深入解析",Content: "本文深入探讨Go语言模板引擎的工作原理和最佳实践...",Summary: "深入探讨Go语言模板引擎的工作原理,包括语法、函数、管道等核心概念。",Author: authors[0],Category: categories[0],Tags: []Tag{tags[0], tags[2], tags[3]},Comments: comments,PublishedAt: "2024-01-15T10:00:00Z",UpdatedAt: "2024-01-15T15:30:00Z",ViewCount: 1250,LikeCount: 89,Status: "published",},{ID: 2,Title: "Web开发最佳实践指南",Content: "本文总结了Web开发中的常见模式和最佳实践...",Summary: "总结Web开发中的常见模式、性能优化技巧和安全最佳实践。",Author: authors[1],Category: categories[1],Tags: []Tag{tags[1], tags[3]},Comments: comments[:2],PublishedAt: "2024-01-14T09:00:00Z",UpdatedAt: "2024-01-14T16:45:00Z",ViewCount: 890,LikeCount: 67,Status: "published",},{ID: 3,Title: "微服务架构设计模式",Content: "探讨微服务架构中的常见设计模式和实现策略...",Summary: "探讨微服务架构中的设计模式,包括服务发现、负载均衡、容错处理等关键技术。",Author: authors[0],Category: categories[2],Tags: []Tag{tags[0], tags[3]},Comments: comments[2:],PublishedAt: "2024-01-13T14:00:00Z",UpdatedAt: "2024-01-13T18:20:00Z",ViewCount: 2100,LikeCount: 156,Status: "published",},}blogSite := &BlogSite{Title: "技术博客",Description: "分享Go语言、Web开发和架构设计的技术文章",Articles: articles,Categories: categories,Tags: tags,Authors: authors,Stats: map[string]int{"总文章数": len(articles),"总浏览量": 4240,"总点赞数": 312,"总评论数": 8,},Config: map[string]string{"theme": "light","language": "zh-CN","timezone": "Asia/Shanghai",},}bts.SetupBlogTemplates()// 演示各种模板templates := []struct {name stringisHTML bool}{{"article_list", false},{"article_detail", false},{"category_stats", false},{"author_profile", false},{"article_cards_html", true},}for _, tmpl := range templates {fmt.Printf("\n" + strings.Repeat("=", 70))fmt.Printf("\n%s 模板结果 (%s)", tmpl.name, map[bool]string{true: "HTML", false: "文本"}[tmpl.isHTML])fmt.Printf("\n" + strings.Repeat("=", 70) + "\n")var data interface{}if tmpl.name == "article_detail" {data = articles[0] // 使用第一篇文章作为详情示例} else {data = blogSite}result, err := bts.ExecuteTemplate(tmpl.name, data, tmpl.isHTML)if err != nil {fmt.Printf("执行模板失败: %v\n", err)continue}fmt.Print(result)}
}// 主函数演示
func main() {fmt.Println("=== Go Web 编程快速入门 · 07 - 模板(1):语法与最佳实践 ===\n")// 1. 安全性演示fmt.Println("1. 模板安全性演示")fmt.Println(strings.Repeat("-", 50))securityDemo := NewSecurityDemo()securityDemo.DemonstrateEscaping()// 2. 数据访问演示fmt.Println("\n\n2. 数据访问演示")fmt.Println(strings.Repeat("-", 50))dataDemo := NewDataAccessDemo()dataDemo.DemonstrateDataAccess()// 3. 条件控制演示fmt.Println("\n\n3. 条件控制演示")fmt.Println(strings.Repeat("-", 50))conditionalDemo := NewConditionalDemo()conditionalDemo.DemonstrateConditionals()// 4. 循环控制演示fmt.Println("\n\n4. 循环控制演示")fmt.Println(strings.Repeat("-", 50))loopDemo := NewLoopDemo()loopDemo.DemonstrateLoops()// 5. 内置函数演示fmt.Println("\n\n5. 内置函数演示")fmt.Println(strings.Repeat("-", 50))builtinDemo := NewBuiltinFunctionDemo()builtinDemo.DemonstrateBuiltinFunctions()// 6. 自定义函数演示fmt.Println("\n\n6. 自定义函数演示")fmt.Println(strings.Repeat("-", 50))customDemo := NewCustomFunctionDemo()customDemo.DemonstrateCustomFunctions()// 7. 博客系统综合演示fmt.Println("\n\n7. 博客系统综合演示")fmt.Println(strings.Repeat("-", 50))blogSystem := NewBlogTemplateSystem()blogSystem.DemonstrateBlogSystem()
}
5. 模板最佳实践
5.1 性能优化
- 模板预编译:在应用启动时预编译所有模板,避免运行时解析
- 模板缓存:缓存已解析的模板,避免重复解析
- 数据预处理:在模板执行前预处理数据,减少模板中的复杂计算
- 合理使用函数:避免在模板中进行复杂的数据处理
5.2 安全考虑
- 选择合适的模板包:Web应用使用
html/template,确保自动转义 - 验证用户输入:对用户提供的模板内容进行严格验证
- 限制模板功能:避免在模板中暴露敏感的系统功能
- 内容安全策略:配合CSP头部增强安全性
5.3 代码组织
- 模板分离:将模板文件与Go代码分离,便于维护
- 模块化设计:使用模板继承和包含机制,提高复用性
- 命名规范:采用一致的模板命名规范
- 文档完善:为复杂模板提供详细的使用文档
5.4 错误处理
- 优雅降级:模板执行失败时提供备用方案
- 详细日志:记录模板执行过程中的错误信息
- 用户友好:向用户展示友好的错误信息
- 监控告警:对模板错误进行监控和告警
总结
本章深入探讨了Go模板系统的语法基础和最佳实践:
- 模板系统概述:了解了
text/template和html/template的区别,掌握了模板安全性的重要性 - 语法基础:掌握了变量访问、条件控制、循环控制等核心语法
- 函数系统:学习了内置函数的使用和自定义函数的实现
- 综合实践:通过博客系统项目,将所学知识融会贯通
- 最佳实践:了解了性能优化、安全考虑、代码组织等实践经验
下一章我们将深入探讨Go Web 编程快速入门 · 07.2 - 模板(2):解析与执行(含Demo),学习模板的解析机制、执行流程和高级特性。反反复复烦烦烦烦烦烦烦烦烦烦烦烦烦烦烦
