正确做法是用 redis 或数据库记录发送时间戳,发前查 verify:phone:138*1234,校验当前时间与上次发送时间间隔是否达标,而非用 sleep() 阻塞进程。

用 sleep() 实现验证码冷却?别这么做
sleep() 会阻塞当前 PHP 进程,用户请求卡住几秒,服务器并发能力直接下降。更严重的是:它无法真正防止刷接口——攻击者开多个线程或用异步请求,照样能绕过。冷却逻辑必须依赖服务端可验证、不可篡改的状态,而不是让脚本“停一下”。
正确做法:用 Redis 或数据库记录发送时间戳
核心是「记录上次发送时间」+「每次请求前校验间隔」,不是靠延迟响应来凑时间。推荐用 Redis,速度快、支持过期自动清理:
- 发验证码前查
GET verify:phone:138****1234,拿到上一次发送的 Unix 时间戳 - 若存在且
time() - $last_time ,直接返回错误:“操作太频繁,请60秒后重试” - 校验通过后,用
SET verify:phone:138****1234 {time()} EX 300写入并设 5 分钟过期(防 key 永久堆积)
注意:EX 参数必须设,否则冷却状态永不消失;手机号要做标准化处理(去空格、统一前缀),避免相同号码因格式不同被当成两个 key。
为什么不用 Session 或文件存冷却时间?
Session 依赖客户端 cookie,容易伪造或丢失;文件写入有并发锁和 I/O 延迟问题,高并发下可能出错。Redis 的原子性 SET ... NX EX 可以一步完成“仅当不存在时设置 + 设过期”,避免竞态条件。如果实在没 Redis,可用数据库加唯一索引 + INSERT ... ON DUPLICATE KEY UPDATE 替代,但性能差一到两个数量级。
立即学习“PHP免费学习笔记(深入)”;
前端提示和后端校验必须同时做
前端用 JS 倒计时按钮(如 60s 禁用)只是体验优化,完全不可信。后端必须每次都查存储、重新计算间隔。常见翻车点:只在生成验证码时校验,却忘了在验证环节也检查该手机号是否在冷却期内——攻击者跳过发送步骤,直接爆破验证接口。
冷却逻辑本质是状态控制,不是延时技巧。把时间判断交给存储系统,而不是让 PHP 进程睡着等时间过去。










