注册自定义模板函数必须在LoadHTMLGlob之前,否则调用失败且无提示;函数签名需参数返回值均为导出类型,首字母大写;模板中调用不支持括号、赋值或嵌套;全局注册,不可按模板隔离。

注册自定义模板函数必须在 LoadHTMLGlob 之前
顺序错会导致函数在模板里调用失败,且无任何错误提示——template.FuncMap 注册只影响后续加载的模板。Gin 的 LoadHTMLGlob 或 LoadHTMLFiles 内部会创建新 template.Template 实例,若 FuncMap 没提前绑定,这些实例就用不到你注册的函数。
- ✅ 正确顺序:先
r.SetFuncMap(...),再r.LoadHTMLGlob("templates/**/*") - ❌ 错误写法:把
SetFuncMap放在LoadHTMLGlob后面,或放在某个路由 handler 里 - 注意:如果用了
r.SetHTMLTemplate(html)手动传入 template 实例,则需在template.New(...).Funcs(...)阶段注入,而非调用SetFuncMap
函数签名必须满足 html/template 要求
Go 模板引擎对函数参数和返回值有硬性约束:所有参数和返回值类型必须是导出的(首字母大写),且不能是 func、chan、unsafe.Pointer 等禁止类型。常见翻车点是返回 template.HTML 却忘了 import "html/template",或参数用了 interface{} 导致运行时报 cannot call function with 1 args。
- ✅ 合法示例:
func(str string) template.HTML、func(t time.Time, layout string) string - ❌ 非法签名:
func(v interface{}) string(interface{} 不可序列化)、func() (err error)(无输入但模板调用时写了参数) - 函数名必须首字母大写,否则模板中
{{myfunc}}会静默失败(不报错,也不执行)
模板中调用时不传指针、不加括号、不嵌套赋值
Go 模板语法严格,{{formatTime .CreatedAt "2006-01-02"}} 是合法的,但 {{formatTime (.CreatedAt)}} 或 {{$t := formatTime .CreatedAt}} 都会解析失败。模板函数是纯函数式调用,不支持临时变量、复合表达式或方法链式调用。
- ✅ 正确写法:
{{safeHtml .Content}}、{{toDate .Timestamp "Jan 2"}} - ❌ 常见错误:
{{.User.Name | upper}}(upper是 text/template 函数,html/template 中不可用,除非你显式注册)、{{if eq (len .Items) 0}}...(len不是默认可用函数,需自己注册) - 注意:若函数返回
template.HTML,它会绕过自动转义;返回普通string则仍会被 HTML 转义
全局注册后所有模板共享,无法按路由/模板隔离
SetFuncMap 是 engine 级别操作,一旦注册,所有通过该 gin.Engine 渲染的 HTML 模板都能访问。没有“只为 admin/index.html 注册”这种粒度——这是设计使然,不是 bug。
- 如果不同业务线需要同名不同实现(比如两个
formatDate),只能统一语义、收敛实现,或改名如formatDateISO/formatDateCN - 不要试图在 handler 里动态修改 FuncMap:并发下会 panic,且修改不生效(FuncMap 是初始化时快照)
- 调试技巧:启动时打印
len(r.Delims)和len(r.HTMLRender.(*render.HTMLDebug).Engine.Templates)可辅助确认模板是否重载成功
实际项目里最容易被忽略的是函数签名和调用语法的匹配——看着像能跑,一渲染就空白,查日志也没报错。建议写完函数立刻建个最小模板验证:{{myfunc "test"}},而不是等堆完逻辑再联调。











