推荐使用 base64captcha 库快速生成验证码图片,它封装了生成、存储、校验全流程,默认返回 base64 字符串便于前端直接渲染;需注意 driver 选择、字体加载、capid/capvalue 区分、服务端安全存储(如 redis 带 ttl)及校验时统一大小写与空格处理。

用 github.com/mojocn/base64Captcha 快速生成验证码图片
Go 语言原生不带图形验证码支持,直接手写 PNG + 字体渲染容易踩字体路径、中文乱码、内存泄漏坑。推荐用成熟库 base64Captcha,它把生成、存储、校验三步封装清楚,且默认返回 base64 字符串,前端可直接用 img src="data:image/png;base64,..." 渲染。
安装命令:go get github.com/mojocn/base64Captcha
关键点:
- 生成时需指定
driverDigit(数字)或driverString(字母+数字),避免用driverMath——它返回表达式(如 "3+5="),但用户识别率低,且后端校验逻辑更重 - 字体文件路径必须可读;若用
driverString且含中文,需显式调用SetCustomFonts()加载 .ttf 文件,否则报错font: cannot load font - 生成的
capId是字符串 ID,不是验证码值本身;真正要校验的是capValue,它和capId需配套存入缓存(如 Redis 或 memory)
验证码值怎么安全存储与过期
不能把验证码明文存在 cookie 或前端隐藏域里——这等于白给。必须服务端存储,且带 TTL。
常见错误:
- 用 map 存内存:进程重启就丢,多实例部署时请求打到不同机器就校验失败
- Redis key 不设过期时间:用户没提交,验证码长期占用内存
- key 名硬编码成
"captcha:" + capId,但没统一前缀,后期难清理
建议做法:
- Redis key 格式:
"captcha:" + capId,value 是小写capValue(校验时统一转小写防大小写误判) - 设置过期时间:一般 5 分钟足够,用
SET key value EX 300 - 如果不用 Redis,可用
github.com/patrickmn/go-cache做本地内存缓存,但仅限单机开发环境
base64Captcha.GenerateCaptcha() 返回值怎么解析
这个函数返回两个值:id string 和 result *base64Captcha.CaptchaResponse。别只取 result.Base64Blob 就完事——它只是图片数据,id 才是关联校验的关键。
result 结构体字段说明:
-
ID:同返回的第一个参数,是唯一标识,前端必须原样传回 -
Base64Blob:PNG 图片的 base64 字符串,开头是"data:image/png;base64,",可直接塞进<img>的src -
VerifyValue:注意!这是原始未处理的验证码值(可能含空格或大小写混杂),**不是**最终校验用的值;实际应存strings.ToLower(strings.TrimSpace(verifyValue))
示例片段:
id, resp := base64Captcha.GenerateCaptcha()
// 存 Redis
redisClient.Set(ctx, "captcha:"+id, strings.ToLower(strings.TrimSpace(resp.VerifyValue)), 300*time.Second)
// 返回 JSON
json.NewEncoder(w).Encode(map[string]string{
"captcha_id": id,
"image": resp.Base64Blob,
})
校验时为什么总提示“验证码错误”
90% 的问题出在校验环节的字符串处理不一致,而非生成逻辑。
典型原因:
- 前端提交的验证码没 trim 空格,用户无意中多按了空格键;后端没
strings.TrimSpace() - 前端输入框设了
text-transform: uppercase,但后端比对时没转大写,导致"Abc"≠"abc" - Redis 取出来的值是
nil或空字符串,但代码没判断就直接比对,结果恒为 false - 校验接口没做限流,被暴力扫号,建议加 IP + captcha_id 组合的短时计数(如 1 分钟最多 5 次)
校验逻辑必须包含三步:
- 检查
captcha_id是否非空 - 从 Redis 取值,若为
nil或空,直接返回错误(说明已过期或不存在) - 将用户提交值和 Redis 值都转小写 + 去首尾空格后再比较
这个细节漏掉一个,前端看着图是对的,后端永远返回“错误”。











