自定义函数必须在Parse前注册到revel.TemplateLoader.Templates.Funcs,且函数名首字母大写、签名合规、无状态纯计算;注册须在app/init.go的init()中完成,否则渲染时panic。

自定义函数必须在 Parse 前注册,且只能用导出函数
Revel 框架底层用的是 Go 标准库的 html/template,所以它不支持“模板里随便写个函数名就调用”的行为。你写的 Go 函数必须显式注册进模板实例,否则渲染时直接 panic:function "myfunc" not defined。
注册动作必须发生在 Parse 或 ParseFiles 之前;Revel 的模板加载流程(比如 c.Render())内部会调用 template.ParseFiles,所以你不能等控制器里再注册——得提前在初始化阶段干这事。
- 函数名必须首字母大写(如
FormatTime),小写函数会被静默忽略 - 不能传匿名函数或闭包,
template.FuncMap只接受具名、可导出、签名合规的函数值 - 推荐在
app/init.go的init()或revel.InterceptFunc前注册,确保所有模板都能用
Revel 中注册 FuncMap 的正确位置和写法
Revel 没有提供全局模板实例的直接入口,但你可以通过修改 Revel 的模板引擎配置来注入函数。最稳妥的方式是在 app/init.go 的 init() 函数中,用 revel.TemplateLoader 获取根模板后挂载:
func init() {
revel.InterceptFunc(func(c *revel.Controller, fc []revel.Filter) revel.Result {
return nil
}, revel.BEFORE)
// 注册自定义函数到 Revel 全局模板
revel.TemplateLoader.Templates.Funcs(template.FuncMap{
"ftime": FormatTime,
"upper": strings.ToUpper,
})
}
注意:revel.TemplateLoader.Templates 是 Revel 初始化后持有的根 *template.Template 实例,对它调用 Funcs() 才能让所有后续 ParseFiles 加载的模板都识别这些函数。
- 别在控制器里 new 一个 template 再
Funcs()——那只是临时实例,和 Revel 渲染链无关 - 别在
c.Render()后补注册——此时模板早已 Parse 完,AST 固化,加了也白加 - 如果用了
template.Must(t.Clone().Funcs(...)),记得把克隆后的实例重新赋给revel.TemplateLoader.Templates
函数签名必须严格匹配模板调用上下文
Revel 模板里调用函数时不做类型推导或自动转换,参数全靠反射传入,稍有不匹配就 panic。比如你在模板写 {{.CreatedAt | ftime}},那 ftime 的签名必须是 func(time.Time) string;如果 .CreatedAt 实际是字符串,就会报错:reflect: Call using *string as type time.Time。
- 返回值最多两个:第一个是结果,第二个要是
error类型(可选),多一个就 panic:function ftime has wrong number of returns - 想返回 HTML 片段避免转义?返回类型必须是
template.HTML,不能是string - 模板传参一律是
interface{},函数体内需手动做类型断言,比如v, ok := arg.(string)
range 里传索引给子模板时,别让自定义函数干扰变量作用域
Revel 模板常用 {{range $i, $v := .Items}} 遍历并传索引,但如果在 range 块里调用自定义函数(比如 {{ftime $v.CreatedAt}}),要注意:函数执行不会污染 $i 或 $v,但如果你在函数里用了同名局部变量(比如也叫 $i),可能引发混淆。
更隐蔽的问题是:某些自定义函数若内部修改了全局状态(比如缓存 map),在并发请求下可能产生数据竞争——Revel 模板执行是并发的,函数必须是无状态、纯计算的。
- 永远用
$i和$v这种带$前缀的显式命名,避免和模板内置变量(如.Title)冲突 - 不要在自定义函数里读写
revel.Controller或c.Params—— 模板函数收不到控制器上下文 - 函数内禁止 IO、网络调用、锁操作;它只该做格式化、拼接、计算这类轻量事
最容易被忽略的一点:Revel 的 template.ParseFiles 默认按文件路径 glob 加载,但 FuncMap 绑定的是 AST 解析阶段;哪怕你改了函数实现、重启服务,只要没改模板文件内容,Revel 就可能复用已缓存的 parsed 模板——这时新注册的函数依然不可见。开发期建议关掉模板缓存:revel.Config.Set("template.cache", "false")。


















