直接用原生 image 包更轻量可控,需搭配 golang.org/x/image/font 加载固定字体、随机单字旋转、适量噪点,并通过 redis 存储带 session 绑定的验证码文本与短期 ttl。

验证码图片生成要用 github.com/disintegration/imaging 还是原生 image 包?
直接用原生 image 包更轻量、可控,第三方库反而容易引入字体渲染不一致、跨平台乱码或内存泄漏问题。验证码不需要复杂滤镜,核心是「文字扭曲不可预测 + 背景噪点不可识别」,原生包完全够用。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
image.NewRGBA创建画布,尺寸固定(如 120×40),避免前端布局抖动 - 文字绘制必须用
golang.org/x/image/font/basicfont+golang.org/x/image/font/inconsolata字体,系统字体路径不可靠 - 每个字符单独随机旋转(
-15到+15度),别用整张图旋转——会留大片空白导致 ocr 容易切分 - 噪点用
draw.Draw随机画 1px 点,数量控制在 30–80 个,太多影响可读性,太少防不住简单脚本
http.HandlerFunc 中怎么安全返回验证码图片和对应文本?
不能把验证码明文塞进 cookie 或响应头,也不能只存在内存里——多实例部署时会失效。必须用服务端存储 + 短期 TTL。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 生成验证码后,用
rand.Read生成 16 字节随机 ID(如base32.StdEncoding.EncodeToString编码成 26 位字符串)作为 key - 存到 Redis,key 是验证码 ID,value 是纯文本(如
"K7mP"),过期时间设为 5 分钟(EXPIRE) - 响应头必须设
Content-Type: image/png,且禁用缓存:w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") - 前端请求时带这个 ID(如
/captcha?id=K7mP_abc123),后端校验时只比对 Redis 中的 value,不碰原始字符串
用户提交表单时,如何防止验证码被重放或错配?
常见错误是:校验完就删 Redis key,结果用户连点两次提交,第二次直接失败;或者没绑定 session,导致 A 用户拿到 B 的验证码 ID 也能通过。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 校验成功后不要立刻
DEL,改用GETSET原子操作取值并设空值,避免并发重复提交 - 验证码 ID 必须和用户当前请求的
session id绑定(比如 Redis key 设为captcha:{session_id}:{captcha_id}) - 前端提交时,除了传验证码 ID 和用户输入,还要附带当前时间戳(秒级)和简单签名(如
hmac-sha256(captcha_id + timestamp, secret)),后端验证时间差 ≤ 30 秒且签名有效 - 每次生成新验证码前,先清理该 session 下旧的未使用验证码 key(用
SCAN+DEL)
为什么本地调试正常,上线后验证码图片显示为黑块或方框?
90% 是字体缺失或编码问题。Go 的 golang.org/x/image/font 不读系统字体,而默认字体(basicfont.Face7x13)在中文环境会 fallback 到方框。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 确认你用了
inconsolata.Font或其他明确加载的字体变量,而不是只 import 了包 - linux 服务器上禁止依赖
fontconfig或freetype,所有字体数据必须打包进二进制(用//go:embed加载 .ttf 文件) - 检查图片编码步骤:调用
png.Encode前,确保rgba.Bounds()覆盖整个画布,否则部分区域是零值(黑色) - 加一行日志:
log.printf("captcha generated: %s, size: %v", captchaText, rgba.Bounds()),上线后看是否 bounds 异常
最麻烦的其实是时区和 Redis 连接超时配置——验证码逻辑里任何一次阻塞超过 2 秒,用户看到的就是空白图片或 500 错误,这点很容易被忽略。