模板函数必须通过setfuncmap全局注册且早于模板加载,签名须首字母大写、返回值最多两个(第二个为error),调用时推荐直接调用如{{formattime .createdat}}。

模板函数必须用 SetFuncMap 全局注册,不能塞进 c.HTML() 的数据参数里
Go 模板引擎禁止在渲染时动态传入函数值,c.HTML() 第三个参数只接受 map[string]interface{} 或结构体这类纯数据类型。试图直接传函数会 panic:「cannot call non-function」或「invalid value; expected function」。真正有效的路径只有一条:在 Gin 路由引擎启动初期,用 engine.SetFuncMap() 注册到全局 template.FuncMap 中。
- 注册时机很关键:必须在
r.LoadHTMLGlob()或r.LoadHTMLFiles()之前,否则模板解析时无法识别新函数 - 函数签名必须是首字母大写的导出函数,且返回值最多两个,第二个必须是
error - 函数名就是模板中调用的标识符,比如注册
"FormatTime",模板里就写{{FormatTime .ts}}
FuncMap 里函数的签名和常见错误
模板函数不是普通 Go 函数——它受 html/template 的调用约束。典型错误包括:参数类型不匹配、返回 error 但没声明、或用了未导出方法。
- 正确示例:
func FormatTime(ts int64) string { return time.Unix(ts, 0).Format("2006-01-02") } - 错误写法:
func formatTime(...)(小写首字母,模板不可见) - 错误写法:
func FormatTime(ts int64) (string, error)但没处理 error 分支,模板里会静默失败 - 错误写法:
func FormatTime(ts interface{}) string(模板传参类型不确定,运行时报can't convert interface {} to int64)
模板里怎么调用自定义函数:管道 vs 直接调用
两种语法都合法,但语义和可读性差异明显。多数场景推荐直接调用,尤其当逻辑清晰、参数明确时。
- 直接调用:
{{FormatTime .CreatedAt}}—— 明确、易调试、IDE 可跳转 - 管道调用:
{{.CreatedAt | FormatTime}}—— 适合链式处理,比如{{.Title | htmlUnescape | truncate 20}} - 注意:管道左侧值会作为第一个参数传入函数,所以
FormatTime必须接收一个参数;若函数需多个参数,只能用直接调用,如{{FormatTime .CreatedAt "2006-01-02 15:04"}}
多个模板共用同一组函数,但不同环境要隔离?
Gin 的 SetFuncMap 是全局单例行为,所有模板共享同一份函数映射表。如果你需要按路由/环境区分行为(比如开发环境加 dump 函数,生产禁用),不能靠注册多个 FuncMap 实现——引擎不支持运行时切换。
- 可行方案:在函数内部判断
os.Getenv("GIN_MODE") == "debug",非 debug 环境直接返回空字符串或原样透传 - 更干净的做法:用中间件预处理数据,把格式化结果算好塞进
c.HTML()的 data 参数里,模板里只做展示,不承担逻辑 - 别踩的坑:不要在每个 handler 里重复
template.New().Funcs(...).Parse(...),这绕过了 Gin 的模板缓存,性能差且无法复用已加载模板











