Gin模板中禁止在{{}}内拼接URL,应由后端组装完整URL传入;必须确保模板正确加载且路径匹配;带参链接需转义防XSS;路由前缀需手动处理。

模板里用 {{.Name}} 直接插值就行,别拼 URL 字符串
Gin 的 HTML 模板不支持在 {{}} 里执行 Go 表达式(比如字符串拼接),所以不能写 {{ "/user/" + .ID }} 这种。正确做法是把完整 URL 当作字段传进去,或者用模板变量直接替换占位符。
- 推荐方式:后端组装好链接,作为字段传入
gin.H,例如c.HTML(200, "list.tmpl", gin.H{"EditURL": "/user/" + id}),模板里直接{{.EditURL}} - 次选方式:前端用变量拼,比如
<a href="https://www.php.cn/link/fd8a0deb9faa2516fc4fffc1669ac74d">编辑</a>—— 这要求.ID是安全字符串(不含斜杠、引号等),且路由确实是/user/:id形式 - 绝对不要在模板里用
{{printf "/user/%s" .ID}},Gin 默认禁用printf等函数,开启需手动注册,不值得为这点便利加风险
c.HTML() 渲染时必须确保模板已加载且文件名匹配
如果链接渲染出来是空的或 404,大概率不是拼接问题,而是模板根本没加载成功。Gin 不会报错提示“模板未找到”,只会静默返回空响应或 panic(取决于模式)。
- 检查
r.LoadHTMLGlob("templates/**/*")路径是否包含你的模板文件,注意是相对go run执行目录的路径 - 确认
c.HTML()第二个参数(模板名)和磁盘上文件名**完全一致**,包括大小写和后缀,例如"user/edit.tmpl"对应templates/user/edit.tmpl - 开发阶段建议用
gin.SetMode(gin.DebugMode),它会在模板缺失时返回明确错误,而不是空白页
带查询参数的跳转链接要手动拼,但得防 XSS
比如跳转到 /search?keyword=xxx&page=2,不能靠 .Keyword 直接插进 URL,否则用户输入 xxx"><script>alert(1)</script> 就会触发 XSS。
- 用
c.QueryEscape()或url.QueryEscape()在后端转义后再拼,例如:"search?keyword=" + url.QueryEscape(keyword) + "&page=" + strconv.Itoa(page) - 更稳妥的是用
url.URL和url.Values构建:u := &url.URL{Path: "/search"}; u.RawQuery = v.Encode(),避免手拼出错 - 模板里只接收最终的
SafeURL字段,不暴露原始参数 —— 安全边界要划在 handler 层,别甩给模板处理
路由组前缀会影响实际跳转路径,但模板里看不到
如果你用了 r.Group("/api/v1"),那 /user/123 实际访问地址是 /api/v1/user/123。但模板里写的 https://www.php.cn/link/fd8a0deb9faa2516fc4fffc1669ac74d 不会自动加前缀 —— Gin 不做路径重写,它只管注册和匹配。
- 方案一:后端拼完整路径,比如
APIPrefix + "/user/" + id,再传给模板 - 方案二:前端用 JS 动态补前缀(适合前后端分离),但 SSR 场景下不适用
- 方案三:把前缀设为配置项,统一注入到所有模板上下文,避免硬编码散落各处


















