直接将 base64Captcha.Generate() 返回的 b64s 字符串(需 HTML 不转义,用 {{.CaptchaImg | safeHTML}})嵌入 img 标签的 src 属性,格式为 data:image/png;base64,...,避免额外 HTTP 请求;同时必须将 id 与 b64s 一同传入模板,并在前端刷新时通过 AJAX 获取新 id 和新 base64 图片并更新隐藏域及 img src,确保校验时 ID 与答案匹配。

base64Captcha 生成的验证码怎么塞进 HTML 模板里
直接把 base64Captcha.Generate() 返回的 b64s 字符串传给 Gin 的 c.HTML(),前端用 <img src="data:image/png;base64,<%= .CaptchaImg %>"> 就能渲染。别拼接 URL 请求图片——它本来就是 base64,再走一次 HTTP 请求纯属多此一举,还容易因 CORS 或缓存出问题。
关键点是:后端必须把 id 和 b64s 一起传入模板,且确保 b64s 是未经转义的原始字符串(Gin 默认会 HTML 转义,得用 {{.CaptchaImg | safeHTML}})。
- 模板中不能写
<img src="{{.CaptchaImg}}">—— 会被转义成/类字符,图片炸掉 -
b64s开头一定是data:image/png;base64,,长度通常在 1500–3000 字节之间,太短说明生成失败 - 如果用了自定义字体但没 embed,
Generate()可能静默返回空字符串或 panic,开发期务必加 error 检查
Gin HTML 模板里怎么触发验证码刷新
刷新不是靠重载页面,而是用 JS 换掉 <img> 的 src。每次点击「换一换」时,先调用后端 /captcha 接口拿到新 id 和新 b64s,再更新 DOM。别用 img.src += '?t=' + Date.now() 这种伪刷新——后端根本没换答案,纯属骗自己。
典型流程:
- 点击按钮 → 发起
GET /captcha(无参数)→ 后端调captcha.Generate()→ 返回 JSON:{"id": "xxx", "image": "data:image/..."} - JS 把返回的
id存进隐藏域(如<input type="hidden" name="captcha_id" value="xxx">),同时替换<img>的src - 表单提交时,
captcha_id和用户输入的captcha_value一起发过去
漏掉隐藏域存 id 是最常见错误:前端刷了图,但提交时带的还是旧 ID,校验必然失败。
为什么不能在 Gin 模板里用 c.Redirect() 跳转生成验证码
因为 Redirect 是 302,会丢失当前请求上下文里的 session、body、query 等数据。验证码生成必须和后续登录请求在逻辑上绑定——ID 和答案要存在同一个 store 实例里,且生命周期对齐。用跳转等于把生成和使用切成两个无关联请求,store 查不到答案。
正确姿势只有两种:
- 首次加载登录页时,后端同步生成验证码并注入模板(推荐)
- 登录页用 JS 异步拉取验证码(适合 SPA 风格)
试图用中间件拦截登录页、自动重定向到验证码接口,属于典型的设计错位——Gin 中间件不负责内容生成,只做前置检查。
模板里展示验证码时要注意宽高和防缓存
<img> 标签必须显式设 width 和 height,否则 base64 图片可能被浏览器压缩变形。建议和 DriverDigit 初始化时的 width/height 严格一致(如 120×40)。
防缓存不是靠加随机参数,而是靠服务端响应头。Gin 中生成验证码接口需手动设置:
func Captcha(c *gin.Context) {
id, b64s, err := captchaInstance.Generate()
if err != nil {
c.JSON(500, gin.H{"error": "gen failed"})
return
}
c.Header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
c.JSON(200, gin.H{"id": id, "image": b64s})
}
不设这个头,Chrome 有时会复用上一张图的 base64 数据,导致用户看到的和后端存的答案对不上。


















