gin中模板用slice截字符串会乱码或panic,因其按字节而非字符切;正确做法是在handler中用rune预处理截取并校验边界后传入模板。

在 Gin 框架中直接用模板语法截取字符串,**不安全且极易乱码或 panic**——因为 Go 模板里的 .Name 是 string 类型,而模板内置的 slice 函数按字节切,不是按字符切。
Go 模板里用 slice 截字符串为什么出错
Go 模板的 slice 函数底层调用的是 s[i:j] 字节切片,和代码里直接写 s[2:5] 一样危险。比如:
-
{{ slice .Title 0 10 }}对于"Gin 处理中文 ✅",可能在某个汉字中间切断,输出无效 UTF-8 - 若
.Title是"你好"(6 字节),{{ slice .Title 0 4 }}会取前 4 字节,解码失败,浏览器显示 - 若
.Title是空或长度不足,slice不做越界保护,直接触发模板执行 panic
正确做法:在后端预处理,传 rune 安全截取后的值
模板层不负责逻辑,只负责展示。所有字符串截取必须在 handler 中完成,转成合法 string 后再传入模板。
- 定义一个安全截取函数:
func Substr(s string, start, length int) string,内部用[]rune(s)转换 - 务必校验边界:
if start ,<code>end := min(start+length, len(runes)) - 对空字符串、BOM(
\uFEFF)、nil *string做前置判断,避免 panic - 示例用法:
c.HTML(200, "index.html", gin.H{"title": Substr(data.Title, 0, 15)})
如果非要模板内用,必须自定义函数注册到 Gin
可以注册一个安全的模板函数,但要注意它仍需走 []rune 路径,不能绕过字符语义。
- 注册时用
router.SetFuncMap(template.FuncMap{"substr": Substr}) - 模板中写:
{{ substr .Title 0 12 }},效果等同于后端调用 - 不要试图用
len .Title判断——它返回字节数,utf8.RuneCountInString才是字符数 - 高频渲染场景(如列表页百条数据)慎用,每次调用都触发一次
[]rune解码,有性能开销
最常被忽略的一点:前端传来的字符串可能带 BOM 或含非法 UTF-8 序列,[]rune(s) 遇到非法字节会静默截断。生产环境建议先用 utf8.ValidString(s) 校验,或用 strings.ToValidUTF8(Go 1.26+)兜底。











