没有不值得去解决的问题,也没有不值得去学习的技术!

Go 模板引擎入门

Go 模板篇

Go 模板引擎入门

(1) Go 模板引擎入门


本系列文章记录作者学习 Go 标准库的心得与实战经验。本篇从 Go 内置的模板引擎 html/templatetext/template 入手,带您快速掌握核心概念与实际用法,并穿插 PHP 转 Go 的开发者视角。

引言:从 PHP 混编到 Go 模板分离

如果您和我一样从 PHP 转向 Go,一定会对”模板引擎”这四个字有着复杂的感情。PHP 本身就是一种模板引擎——你可以随时在 HTML 中嵌入 <?php ?> 标签,混入业务逻辑。Laravel 的 Blade、ThinkPHP 的 ThinkTemplate 等,其实是对这种原生混编能力的进一步封装和约束。

而 Go 语言走出了另一条路。它没有把模板系统内建到语言语法层面,而是在标准库中提供了两个独立的模板引擎包:text/templatehtml/template。这意味着模板渲染是显式的、可控的、类型安全的。

这种设计理念很 Go:把事情做简单、做清晰,把复杂的选择留给开发者

一、Go 的模板引擎是什么

Go 官方提供的模板引擎,本质上是一个 数据驱动的文本生成器。它将”模板”(带有占位符和指令的文本)与”数据”(Go 中的任意结构体、map、变量等)结合在一起,生成最终的输出内容。

在 Web 开发中,模板引擎的作用流程是这样的:

  1. 定义模板文件:创建一个 tmpl.html 文件,里面嵌入指令(如 {{ . }})。
  2. 解析模板文件:调用 template.ParseFiles() 加载模板。
  3. 传入数据执行:调用 t.Execute() 生成最终输出。

📁 模板文件后缀推荐

Go 语言官方对模板文件后缀没有强制要求。但从项目可维护性和跨编辑器兼容性出发,社区逐渐形成了推荐约定:

后缀说明
.gotmpl最佳推荐。Go 官方语言服务器 gopls 默认识别,语义清晰,跨 IDE 一致性好。
.tmpl历史常用,但部分编辑器需要手动配置语法高亮。
.gohtml适用于 HTML 输出场景,对前端工具友好。

新项目建议统一使用 .gotmpl。本文示例为兼容常见教程,仍使用 .html 后缀,你可以按项目规范自行调整。

Go 的模板引擎介于”无业务逻辑”和”嵌入业务逻辑”之间——它支持条件判断、循环等基本控制结构,但不鼓励在模板中塞入复杂的业务逻辑。这是一种务实的取舍。

二、两个模板引擎:text/template vs html/template

Go 提供了两个模板包,它们共用同一套 API 和语法,但定位截然不同:

包名适用场景安全特性
text/template纯文本输出(配置文件、脚本、代码生成、日志等)无自动转义,按原样输出
html/templateWeb 应用中的 HTML 输出上下文感知自动转义,防止 XSS 攻击

核心原则是:输出 HTML 就用 html/template,输出纯文本就用 text/template,千万不要混用。

初识 html/template:一个最小示例

创建 tmpl.html 文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go 模板示例</title>
</head>
<body>
    <!-- {{ . }} 是最基本的动作,输出传入的数据 -->
    <h1>{{ . }}</h1>
</body>
</html>

Go 服务端代码:

package main

import (
    "html/template"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // 1. 解析模板文件,返回 *Template 指针
    t, _ := template.ParseFiles("tmpl.html")
    // 2. 执行模板,将数据写入 ResponseWriter
    t.Execute(w, "Hello, Go 模板引擎!")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

将上面的代码保存为 main.go,并在同级目录下创建 tmpl.html 文件(内容如前所示)。然后执行:

go run main.go

终端会输出类似以下信息(没有报错即表示服务启动成功):

# 无输出表示成功,或者可以添加 log.Println 主动提示

此时,打开你的浏览器,在地址栏输入:

http://localhost:8080

按下回车,浏览器页面中就会显示:

Hello, Go 模板引擎!

如果你的页面看到这段文字,说明你的第一个 Go 模板引擎示例已经成功运行。如图1

如果你的页面看到这段文字,说明你的第一个 Go 模板引擎示例已经成功运行。如图1

用 Must 处理错误

上面的示例故意省略了错误处理(t, _ := ...),这在生产环境是不安全的。标准库提供了一个便捷函数 template.Must:如果解析模板时发生错误,它会直接触发 panic,避免后续调用 Execute 时出现 nil 指针。

t := template.Must(template.ParseFiles("tmpl.html"))
// 现在可以放心地调用 t.Execute

这个设计非常适合在 init()main() 的全局初始化阶段使用。

三、深入了解模板的执行机制

3.1 解析模板的多种方式

template 包提供了多个函数来解析模板源:

  • template.ParseFiles(filename ...string):解析一个或多个模板文件。如果传入多个文件,返回的 *Template第一个文件名作为模板名,其余文件作为关联模板存储。
  • template.ParseGlob(pattern string):使用通配符批量解析模板,例如 template.ParseGlob("templates/*.html")
  • template.New(name).Parse(text):先创建一个命名模板,再从字符串中解析。

3.2 执行模板的两种方式

当模板集合中有多个模板时,就需要区分执行方式:

  • t.Execute(w, data):执行集合中的 默认模板(通常是通过 ParseFiles 传入的第一个文件)。
  • t.ExecuteTemplate(w, name, data):根据模板名称执行指定的模板。
// 示例:ParseFiles 传入了两个文件,但默认模板是第一个
t := template.Must(template.ParseFiles("layout.html", "content.html"))
// 执行 layout.html,而非自动执行为第一个
t.ExecuteTemplate(w, "layout.html", data)

3.3 模板命名与作用域

Go 的模板系统还有一个关键点:*template.Template 本质上是一个命名的模板集合,而不是单一模板

// 创建并添加多个命名模板
t := template.New("base")
t = template.Must(t.Parse(`{{define "header"}}头部内容{{end}}`))
t = template.Must(t.Parse(`{{define "footer"}}底部内容{{end}}`))

// 执行任意命名模板
t.ExecuteTemplate(os.Stdout, "header", nil)

这个设计为模板复用和布局继承提供了基础。

四、模板语法基础(开篇预览)

第一篇先带你熟悉最核心的两个语法元素,后续文章会深入展开。

4.1 动作(Action):{{ }}

所有模板指令都写在 {{}} 中,称为”动作”。动作内外可以有空格,Go 模板解析器对空格有一定的容忍度。

  • {{ . }}:输出当前数据上下文(点号)。
  • {{ .FieldName }}:访问数据结构的字段,字段名必须首字母大写(导出)。
  • {{ .Method }}:可以调用接收者为当前值的方法。

4.2 变量与管道:{{ $var := .Field }}

Go 模板中可以用 $ 开头的变量来存储中间结果,并通过管道串联多个操作:

{{ $author := .Author.Name }}
{{ $author | printf "作者:%s" }}

管道风格与 Unix 管道神似,可以从左到右串联变换。

4.3 注释:{{/* 注释内容 */}}

模板注释不会出现在最终的输出中,适合用来标注模板的逻辑说明。

{{/* 这里是一段注释,不会输出到 HTML */}}

4.4 从 PHP 视角看 Go 模板语法

场景PHP(Blade)Go 模板
输出变量{{ $name }}{{ .Name }}
循环@foreach($users as $user){{ range .Users }}
条件@if($active){{ if .Active }}
注释{{-- 注释 --}}{{/* 注释 */}}
继承@extends('layout')使用 {{ define }} + {{ template }}

Go 模板的变量访问需要首字母大写(因反射机制的限制),而 PHP 中不存在此约束。这常常是 PHP 转 Go 新手最容易踩的坑。

五、html/template 的安全机制

5.1 自动转义:以安全为默认行为

html/template 最强大的特性是 上下文感知的自动转义。模板引擎在解析时,会分析每个 {{ .Field }} 所在的位置(HTML 标签内、属性值中、JavaScript 代码块、CSS 样式块),然后自动选择最合适的转义策略。

data := struct {
    UserInput string
}{
    UserInput: `<script>alert('XSS')</script>`,
}
t.Execute(w, data)

如果 UserInput 被放在普通的 HTML 标签中(如 <div>{{ .UserInput }}</div>),输出会被转义为:

&lt;div>&amp;lt;script&amp;gt;alert(&amp;#39;XSS&amp;#39;)&amp;lt;/script&amp;gt;&lt;/div>

脚本不会执行,从根本上防御 XSS 攻击。

5.2 何时需要绕过转义:template.HTML 类型

如果你确实需要输出原始 HTML(比如后台富文本编辑器生成的内容),可以使用 template.HTML 类型。但前提是你已经完全信任该内容,并且已经对其进行了必要的净化:

import "html/template"

type PageData struct {
    SafeHTML template.HTML  // 原样输出,不会转义
    RawText  string          // 会被自动转义
}

data := PageData{
    SafeHTML: template.HTML("<strong>加粗文字</strong>"),
    RawText:  "<strong>会被转义</strong>",
}

5.3 Go 1.26 的安全增强

截至本文撰写时,Go 1.26 中 html/template 针对一个特殊的 meta refresh 场景有安全增强:

CVE-2026-27142:将 URL 插入到 <meta http-equiv="refresh" content="url=..."> 的 content 属性中时,某些边界条件可能导致转义失败,存在 XSS 风险。此问题在 1.25.8 和 1.26.1 中已完成修复。

如果你正在使用 Go 1.26.0(不含 1.26.1),建议升级到最新补丁版本。若因特殊原因无法升级,可通过设置 GODEBUG 环境变量 htmlmetacontenturlescape=0 临时禁用该转义机制。

5.4 对比 PHP:XSS 防护的异同

对比维度PHPGo (html/template)
自动转义需要手动调用 htmlspecialchars()默认自动转义(上下文感知)
误用风险开发者可能忘记调用默认安全,安全是常态而非选择
绕过安全{!! $html !!} (Blade)template.HTML 类型标记
可信度要求开发者需要时刻保持警惕类型系统强制安全契约

Go 的做法最本质的差异在于:安全不是”记得做”的选项,而是引擎默认的行为

六、总结

今天我们从零开始,系统性地梳理了 Go 语言内置模板引擎的核心概念:

  1. 两个引擎text/template(纯文本)与 html/template(安全 HTML)——API 相同,场景不同。
  2. 三步流程:定义 → 解析(ParseFiles)→ 执行(Execute),循环往复。
  3. 语法基础{{ . }} 访问数据,{{ $var }} 定义变量,{{/* 注释 */}} 辅助理解。
  4. 安全优势:上下文感知自动转义,从根本上防范 XSS,PHP 开发者必备的新思维。

需要长期技术维护或远程问题排查?

我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。

如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:

  • ✅ PHP / Laravel / Yii2 老项目无人维护
  • ✅ Go / Gin 后端接口需要排查或优化
  • ✅ WordPress 网站访问慢、报错或插件冲突
  • ✅ Nginx / MySQL / Redis / Linux 服务器异常
  • ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
  • ✅ 需要长期远程技术支持或兼职维护

更多介绍请查看:关于我 & 合作

微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理