Revel 模板中用 slice 截字符串危险,因按字节切致中文、emoji 乱码或 panic;必须在 controller 中用 []rune 预处理并校验边界,或注册带 rune 转换的自定义模板函数。

Revel 模板里直接用 slice 截字符串,和 Gin 一样危险——它按字节切,不是按字符切,中文、emoji 一截就乱码或 panic。
你不能依赖模板层做安全截取,必须在 controller 里预处理。
为什么 slice 在 Revel 模板里会出问题
Revel 的模板引擎(基于 Go text/template)中,{{ slice .Title 0 12 }} 底层就是 s[0:12] 字节切片。后果很直接:
- 若
.Title = "你好世界"(12 字节),{{ slice .Title 0 5 }}可能卡在“好”字中间,输出非法 UTF-8,浏览器显示 或空白 - 若
.Title是空字符串或长度不足 5 字节,模板执行直接 panic,HTTP 500 - emoji(如 ?)占 4 字节,
slice切到中间同样解码失败
正确做法:在 Revel controller 中用 []rune 预处理
所有字符串截取逻辑必须移出模板,放到 App.Controller 方法里。关键三步:转 rune → 校验边界 → 转回 string。
- 先用
rs := []rune(s)把字符串拆成 Unicode 码点数组 - 检查
start和end是否越界:start = min(start, len(rs)),end = min(start+length, len(rs)) - 再切再转:
string(rs[start:end]) - 特别注意
nil *string、BOM(\uFEFF)、空串,不判会 panic
示例:
func (c App) Index() revel.Result {
title := "Go语言 ✅ 处理中文标题很长"
c.RenderArgs["title"] = safeSubstr(title, 0, 10) // 传进去的是合法 string
return c.Render()
}
func safeSubstr(s string, start, length int) string {
if s == "" {
return ""
}
rs := []rune(s)
if start >= len(rs) {
return ""
}
end := start + length
if end > len(rs) {
end = len(rs)
}
return string(rs[start:end])
}
如果非要在模板里用,必须注册自定义函数
Revel 支持在 app/init.go 中向模板注册函数,但注意:它仍要走 []rune 路径,不能绕过字符语义。
- 注册位置在
revel.InterceptMethod((*App).Init, revel.BEFORE)之后,或直接在init()里调用revel.TemplateFuncs["substr"] = safeSubstr - 函数签名必须是
func(string, int, int) string,且内部必须含[]rune转换和边界检查 - 模板中写法:
{{ substr .Title 0 12 }},但性能略差于预处理,且错误仍可能发生在模板渲染阶段
容易被忽略的边界细节
很多人只记得转 []rune,却漏掉这些实际部署时才暴露的问题:
-
safeSubstr("", 0, 5)不加空串判断 →len([]rune("")) == 0,但后续rs[0:5]仍 panic - 前端传入带 BOM 的字符串(如 Windows 记事本保存的 UTF-8),
[]rune("\uFEFF你好")第一个 rune 就是 BOM,截取时可能把 BOM 算进前 N 个字符里 - Revel 的
c.RenderArgs是 map[string]interface{},传入nil *string会导致nilpanic,必须提前解引用或判空











