Gin渲染HTML时字段必须首字母大写才能被{{.Field}}访问,且LoadHTMLGlob须在gin.Default()后、路由注册前调用,模板名需严格匹配文件名(含后缀),路径无关。

模板文件里用 {{.Message}} 取值,但字段必须首字母大写
Go 的 html/template 机制要求所有被模板访问的字段必须可导出(即首字母大写),否则渲染时值为空或直接忽略。比如你传入 gin.H{"message": "系统已更新"},模板里写 {{.message}} 会显示空白——因为 message 是小写,不可导出。
正确做法是:用 gin.H{"Message": "系统已更新"} 或定义结构体:
type NoticeData struct {
Message string
Level string
Time time.Time
}
c.HTML(200, "notice.tmpl", NoticeData{
Message: "数据库备份完成",
Level: "success",
Time: time.Now(),
})
模板中对应写 {{.Message}}、{{.Level}} 即可生效。
r.LoadHTMLGlob("templates/**/*.tmpl") 必须在路由注册前调用
模板加载不是“按需触发”,而是启动时一次性注册进引擎的全局行为。如果把 r.LoadHTMLGlob(...) 放在 r.GET(...) 之后,后续所有 c.HTML() 调用都会报错 template: "notice.tmpl" is undefined。
常见错误顺序:
- ❌ 先写路由,再加载模板
- ❌ 在某个 handler 里临时调用
r.LoadHTMLGlob(无效,且重复加载浪费) - ✅ 正确顺序:初始化
r := gin.Default()→ 立即r.LoadHTMLGlob("templates/**/*.tmpl")→ 再注册所有路由
注意通配符:** 表示递归子目录,* 只匹配当前层;通知类模板常放在 templates/notify/ 下,所以推荐用 "templates/**/*" 覆盖全部。
消息内容含 HTML 标签时,用 {{.Message | safeHTML}} 避免转义
默认情况下,{{.Message}} 会自动 HTML 转义(比如把 <b>重要</b> 渲染成纯文本)。如果你的通知消息本身是带格式的富文本(如邮件摘要、日志片段),需要显式声明信任该内容。
模板中这样写:
<div class="notice {{.Level}}">
{{.Message | safeHTML}}
<small>{{.Time.Format "15:04" }}</small>
</div>
无需额外注册函数——safeHTML 是 Go 标准库内置的模板函数。但务必确认 .Message 来源可信(比如内部服务生成,非用户直输),否则有 XSS 风险。
c.HTML() 第二个参数必须是文件名(含后缀),不是路径
假设你的模板实际路径是 templates/notify/email_alert.tmpl,那么 c.HTML(200, "email_alert.tmpl", data) 才对;写成 "notify/email_alert.tmpl" 或 "email_alert" 都会失败。
原因:Gin 加载模板时只记录文件名(不含目录),匹配时只比对 basename。即使你用 LoadHTMLGlob("templates/**/*") 递归加载,也改变不了这个规则。
调试技巧:
- 启动时加日志:在
r.LoadHTMLGlob(...)后打印r.Delims和已加载模板名(需反射或调试模式) - 临时用
r.LoadHTMLFiles("templates/notify/email_alert.tmpl")测试单文件是否能加载成功 - 确保工作目录正确:Go 运行时找的是当前执行路径下的
templates/,不是go build源码路径
最易忽略的一点:模板文件名大小写敏感,Linux 下 Email_Alert.tmpl 和 email_alert.tmpl 是两个不同模板。


















