cache::tag()限流无效因默认文件驱动并发写入竞争,get/set非原子操作致计数漏判;需切redis、带ip等唯一标识键、用inc()原子累加并注意初始值为1。

ThinkPHP 接口被刷时 Cache::tag() 限流为什么没效果
因为 Cache::tag() 默认用的是文件驱动,高并发下缓存写入竞争严重,get() 和 set() 非原子操作,同一秒内多个请求可能都读到旧值,导致计数漏判。别急着换 Redis,先确认驱动是否真生效。
- 检查
config/cache.php中'default'是否设为'redis',且'redis'配置块存在并可连通 - 限流键必须带唯一标识,比如
'rate_limit_' . $request->ip() . '_' . $action,不能只用$action - 用
$cache->inc($key)替代get + set手动累加,Redis 驱动下inc()是原子的;文件驱动不支持inc(),会静默降级,需提前验证 - 注意
inc()初始值:首次调用返回1,不是0,判断逻辑要写成if ($count > $limit),而非>=
验证码防刷中 captcha() 图片不显示或校验总失败
不是验证码逻辑错了,大概率是 Session 写入失败或跨请求丢失。ThinkPHP 的 captcha() 依赖 session 存储验证码原文,而接口常被前端用 fetch 调用,默认不带 cookie,Session 就断了。
- 后端生成验证码时,确保
session_start()已触发(TP6 默认开启,但若在命令行或 CLI 模式下调用会失效) - 前端调用接口前,
fetch必须加{ credentials: 'include' };Axios 要设withCredentials: true -
captcha()默认有效期 30 分钟,但实际应配合限流使用——比如只对每 IP 每 5 分钟允许一次获取,避免被批量拉取 - 校验用
Captcha::check($verify, $id),注意$id若为空字符串,会走默认 id,但并发获取时可能混用,建议显式传参如'login_' . $request->ip()
限流 + 验证码组合策略下,如何避免用户正常操作被误杀
单纯按 IP 限流,在办公网、校园网场景下极易误伤;只靠验证码,又挡不住 OCR 自动识别。得用分层响应:轻量请求放行,可疑行为逐步加压。
- 第一层:对
/api/login这类敏感接口,先查 IP 最近 1 分钟请求数,超 5 次直接返回429并不给验证码机会 - 第二层:未超阈值但 UA 是空或含
python-requests等特征,强制要求携带验证码字段,否则拒绝 - 第三层:验证码校验失败连续 3 次,对该 IP 锁定 15 分钟(用
Cache::set('block_' . $ip, 1, 900)),后续请求直接拦截 - 所有限流 key 建议加上时间戳分片,例如
'req_'.date('i').'_'.$ip,避免单个 key 过热影响 Redis 性能
TP6.1+ 中 middleware 里做限流,$next($request) 前后顺序搞错的后果
限流中间件必须在 SessionInit 之后、业务逻辑之前执行,否则 $request->ip() 可能拿不到真实 IP(被代理头污染),或 Session 尚未初始化导致验证码校验失败。
- 在
app/middleware.php中,把自定义限流中间件放在SessionInit::class后,但要在AllowCrossDomain::class之类响应头中间件之前 - 不要在中间件里直接
return json(['code'=>429]),而要用Response::create(...)->code(429),否则可能绕过后续中间件的 header 设置 - 如果用了多级代理(如 Nginx + SLB),
$request->ip()默认不可信,需在中间件开头手动解析X-Forwarded-For,并白名单信任上游 IP 段 - 测试时用
curl -H "X-Forwarded-For: 1.2.3.4"模拟,别只靠本地127.0.0.1测,真实环境根本不会走这个分支
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










