DefaultMemStore仅限本地调试,生产环境必须替换为带TTL的Redis存储;因其将验证码存于进程内存,多实例、扩缩容或重启后即失效,导致Verify总返回false。

直接用 base64Captcha.DefaultMemStore 只能本地调试,上线多实例或重启后验证码必然失效——这不是 bug,是内存存储的天然限制。
为什么 DefaultMemStore 不能上生产
它把验证码存在进程内存里,Gin 启多个 worker、换机器、服务重启,store 就清空了。用户刚刷出验证码,你一扩缩容,他输对也过不去。常见现象是:前端能拿到 id 和图片,但 store.Verify(id, input, true) 总返回 false,日志还看不到报错——因为 Get 返回空字符串,比对自然失败。
- 单机开发时可用,仅限快速验证流程
- 所有涉及部署、灰度、K8s 自动扩缩容的场景,必须换存储实现
- 别试图给
DefaultMemStore加锁或共享,Go 的 map 不是跨进程的
Redis 存储必须自己实现 Store 接口
base64Captcha.Store 是个接口,只有三个方法:Set、Get、Verify。漏掉任意一个,运行时就会 panic,尤其是 Verify——它内部会调 Get,而未实现的接口方法调用会触发 nil pointer dereference。
-
Set必须传time.Minute * 2这类显式 TTL,不能依赖 Redis 全局配置 -
Get必须判断redis.Nil错误,否则val, err := rdb.Get(ctx, key).Result()在 key 不存在时返回""+redis.Nil,不检查就直接比对,永远 false -
Verify不要另查一次 Redis,应直接复用Get逻辑,并把clear参数透传下去 - key 命名建议用常量前缀,比如
const captchaKeyPrefix = "auth:captcha:",避免和其他业务冲突
context 不能全局复用,每次都要新传
写成 var ctx = context.Background() 看似省事,实际是定时炸弹。Redis 操作超时、取消、链路追踪全失效,且并发下可能被意外 cancel。所有 rdb.Set、rdb.Get 都得带当前请求的 ctx。
- Gin handler 里用
c.Request.Context(),不是context.Background() - 别在
RedisStore结构体里存 ctx 字段,它是 request-scoped 的 - TTL 时间不要硬编码在
Set方法里,应由调用方决定:图形验证码传2 * time.Minute,短信验证码传5 * time.Minute
验证码生成与校验的 Gin 路由写法要点
生成接口返回的 idKey 必须原样传给校验接口,不能做任何截断、trim、大小写转换;后端也不该对用户输入的 code 做自动去空格或转大写——验证码区分大小写是基本安全要求。
- 生成路由示例:
GET /api/v1/captcha,返回{"idKey":"a1b2c3","image":"data:image/png;base64,..."} - 校验路由示例:
POST /api/v1/captcha/verify?id=a1b2c3&code=Ab3xY9,注意 query 参数顺序无关,但 key 名必须匹配 - driver 字体路径如
"wqy-microhei.ttc"要确保二进制运行时能读到,Docker 镜像需 COPY 字体文件 - 调试时加
if gin.Mode() == gin.DebugMode打印答案,但禁止打到生产日志里
最易被忽略的是 Verify 方法里 clear 参数的语义:它不只是“删不删 key”,更决定了是否允许重复验证。设为 true 才符合常规图形验证码“一次性”要求;设为 false 则适合需要多次校验的场景(比如前端防抖重发),但必须你自己控制幂等逻辑。


















