模板加载需用ParseGlob("templates/*.html")或显式列全路径;渲染用户输入须用{{.Content}}触发自动转义,禁用template.HTML包裹不可信内容;结构体字段必须首字母大写才可被模板访问。

模板文件怎么加载才不会 panic: template: … not found
Go 的 html/template 默认不自动递归查找子目录,template.ParseFiles() 只认你显式列出的路径。如果模板放在 templates/ 下,又用了嵌套如 {{template "header"}},但没把 header.html 一起传进去,就会报错 template: "header" is undefined。
- 用
template.ParseGlob("templates/*.html")—— 最省心,匹配所有 HTML 文件并自动注册命名模板 - 或手动列全:
ParseFiles("templates/base.html", "templates/header.html", "templates/index.html")</li> <li>注意路径是相对于 <code>os.Executable()所在目录(即运行时工作目录),不是go run的源码目录;部署时建议用embed.FS避免路径漂移
如何安全渲染用户输入而不被 XSS 攻击
html/template 默认做上下文敏感转义,但前提是变量必须通过 {{.Content}} 这种方式注入。如果误用 {{printf "%s" .Content}} 或拼接字符串再传入,转义就失效了。
- 永远不要用
template.HTML包裹不可信内容,除非你 100% 控制来源(比如 CMS 后台审核过的富文本) - 需要插入 HTML 片段时,明确用
{{.Content | safeHTML}},且确保.Content已经过净化(例如用bluemonday库处理) - URL、CSS、JS 属性值也各自有专属转义逻辑:用
{{.URL | urlquery}}、{{.Style | css}}、{{.Script | js}}
为什么 {{.Name}} 显示为空,但 {{printf "%v" .Name}} 能打出值
常见于结构体字段未导出(首字母小写)。Go 模板只能访问导出字段,type User { name string } 的 name 字段在模板里完全不可见,哪怕 fmt.Printf 能打印出来。
- 检查字段名是否大写开头:
Name string✅,name string❌ - 嵌套结构体也要逐层导出:
User { Profile: profile{Age: 25} }中Profile必须大写,且profile类型里的Age也要大写 - map 类型不受限,
map[string]interface{}里任何 key 都能访问,但注意 key 是字符串,不是变量名
怎样复用 layout 并正确传递数据给子模板
用 {{define}} + {{template}} 是标准做法,但容易漏掉数据传递——子模板默认收不到父模板的 .,除非显式传参。
立即学习“go语言免费学习笔记(深入)”;
- 定义 layout:
{{define "base"}}<html>{{template "content" .}}{{end}} - 调用子模板时必须带点:
{{template "content" .}},否则子模板里{{.Name}}是 nil - 如果子模板需要额外数据(比如页面 title),用
map包一层:{{template "content" (dict "Page" . "Title" "Home")}},需提前注册dict函数 - 避免在
base里直接写{{.Title}}—— 它不属于 base 的数据范围,应该由具体页面控制
{{template}}、一个没导出的字段、一个没走 ParseGlob 的路径,都可能让页面静默失败或渲染错乱。


















