一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Spring Boot如何利用Redis实现验证码限流与存储?

时间:2026-08-16 10:20:49 编辑:袖梨 来源:一聚教程网

必须用StringRedisTemplate.opsForValue().set()显式设过期时间,限流与验证码键须分离;手机号限流需分60秒时间戳键和24小时计数键;校验验证码应使用getAndDelete保证一次性消费。

直接结论:必须用 StringRedisTemplate + opsForValue().set() 显式传入过期时间,不能用 RedisTemplate.set();限流和存储要分键设计,否则会相互干扰甚至误封用户。

为什么 RedisTemplate.set() 不能存带过期时间的验证码

这个方法签名是 set(K key, V value),没有 timeout 参数。你如果写了 redisTemplate.set("key", "123456"),它会存进去但不设 TTL,等同于永久有效(直到手动删或内存淘汰)。更隐蔽的问题是:如果你声明的是 RedisTemplate,value 是字符串却走 JDK 序列化,Redis 里看到的是乱码字节流,get() 返回 null 或报错。

  1. 必须用 StringRedisTemplate(或显式配置 String 序列化器的 RedisTemplate
  2. 存验证码只用这一行:stringRedisTemplate.opsForValue().set("login:code:138xxxx1234", "a7f9k", 5, TimeUnit.MINUTES)
  3. 别依赖“自动过期”幻想——没显式传 TimeUnit,就等于没过期

手机号限流键怎么设计才不踩坑

限流目标不是“禁止发短信”,而是“防刷”:同一手机号 60 秒内最多发 1 次,24 小时最多 3 次。这两个维度必须拆开存,否则用一个 key 做 INCR 会把分钟级和日级计数混在一起。

  1. 60 秒间隔控制:sms:last_send:+8613812345678 存时间戳,EXPIRE 设为 60s,发前先 GET 判断是否距上次发送不足 60 秒
  2. 24 小时频次限制:sms:count:day:+8613812345678INCR + EXPIRE(设 86400s),每次发完自增,超 3 就拒绝
  3. 千万别把两个逻辑塞进同一个 key,比如 sms:limit:+86138... —— 你无法原子地同时更新时间和计数

校验验证码时要不要立刻删 key

只靠 TTL 不够。用户连续输错 5 次,每次校验都 GET 一次,Redis 承担了无效查询压力;更严重的是,如果验证码被截获重放,TTL 内还能反复用。

  1. 推荐用 stringRedisTemplate.opsForValue().getAndDelete("login:code:138xxxx1234")
  2. 它原子性完成“读取 + 删除”,确保一次性消费
  3. 如果返回 null,说明已失效或不存在,直接拒绝登录,不用再查一遍
  4. 注意:getAndDelete 返回的是 String,不是 Boolean,判空用 StringUtils.hasText() 而非 != null

IP 限流和手机号限流能共用一套逻辑吗

不能硬套。IP 是粗粒度防护(防脚本批量请求),手机号是细粒度业务控制(防用户自己刷),二者触发条件、阈值、惩罚方式都不同。

  1. IP 限流 key 示例:sms:ip_limit:192.168.1.100,1 分钟最多 5 次,超限后直接 503
  2. 手机号限流 key 示例:sms:phone_limit:+86138...,60 秒 1 次 + 24 小时 3 次,超限返回明确提示“今日已达上限”
  3. 关键区别:IP 限流失败应快速失败(不进业务逻辑),手机号限流失败要给用户友好提示并记录日志

真正麻烦的不是写几行 Redis 命令,而是键命名规则、过期时间粒度、以及 get/delete 的原子性选择——这些细节一旦错位,轻则限流失效,重则误伤正常用户。别省那几行代码,每个键的用途和生命周期,得在注释里写清楚。

热门栏目