直接结论:throttle中间件在web路由里默认不生效,因它仅是壳,依赖ratelimiter::for()注册的命名策略,而web路由未配置限流且不支持匿名参数;常见原因包括缓存驱动为array/file、未注册策略、key生成未判空或不稳定。

throttle 中间件为什么在 web 路由里完全没反应
直接结论:它默认不生效,不是 bug,是设计如此。Laravel 的 throttle 中间件本身只是个“壳”,所有逻辑都依赖 RateLimiter::for() 注册的命名策略;而 web 路由组压根没被配置为自动启用限流,也不会 fallback 到匿名参数(如 throttle:60,1)。
常见错误现象包括:加了 ->middleware('throttle:60,1') 却狂刷无拦截、返回 200、日志里也看不到计数器变化。根本原因有三:
-
config/cache.php的'default'是array或file—— 多进程下计数器各自独立,限流静默失效 - 没在
app/Providers/RouteServiceProvider.php的configureRateLimiting()里定义对应策略名(比如你写throttle:api,但没注册RateLimiter::for('api', ...)) - web 路由默认不启动 session,
$request->user()为 null,by($request->user()->id)直接抛Call to a member function id() on null
RateLimiter::for() 里 by() 返回值怎么写才安全
by() 决定“谁共享一个计数桶”,写错就等于限流失效或误杀。它必须返回稳定、唯一、无特殊字符的字符串,且不能在不同请求间漂移。
典型坑点:
- 用
$request->ip()—— CDN 后全站用户共用一个公网 IP,游客限流形同虚设 - 用
session_id()—— API 场景 session 通常未开启,返回空或随机值,导致每个请求都进新桶 - 用
$request->user()->id但没判空 —— 匿名请求直接 500 - 拼接字符串含空格或冒号,比如
"user:{$id}"—— 缓存 key 解析失败,计数器乱掉
推荐写法:
return Limit::perMinute(100)->by(
$request->user()?->id ?? 'guest_'.str_replace([':', ' ', '/'], '_', $request->header('X-Forwarded-For') ?: $request->ip())
);
reset_in 参数到底管不管用
reset_in=3600 这类参数只控制响应头里的 X-RateLimit-Reset 值,**不影响实际限流窗口逻辑**。前端看到“1 小时后重置”,后端其实用的是 Redis ZSET 实现的滑动窗口,每分钟滚动统计。
也就是说:
- 设
throttle:60,1,reset_in=86400,不会让计数器真等 24 小时才清零 - 它只是把
X-RateLimit-Reset时间戳往后推 24 小时,对客户端提示友好,但服务端仍按每分钟重算 - 真正决定窗口长度的是
Limit::perMinute()底层实现,不是这个参数
高并发刷量场景下单靠 Laravel throttle 为什么扛不住
因为 PHP 层处理请求太慢(平均 200–500ms),而攻击流量可达 100+ QPS。等请求进到 Laravel,服务器早被拖垮了。
必须分层防御:
- Nginx 层用
limit_req做漏桶限流,毫秒级拦截,扛住洪峰 - Laravel 层用
RateLimiter做精细化策略(按用户角色、设备指纹、tenant_id 等维度) - 业务层加验证码、签名、时间戳兜底,防绕过
特别注意:Nginx 的 limit_req_zone $binary_remote_addr 在 CDN 后会失效,得换成 $http_x_forwarded_for 或自定义 header,并确保 upstream 正确透传。











