laravel 的 throttle 中间件默认仅对 api 路由生效,web 路由需手动配置 ratelimiter 策略;限流 key 必须稳定唯一(如用户 id 或 ip+设备指纹组合),避免误限;reset_in 仅影响响应头,不限制实际窗口逻辑。

直接用 Laravel 自带的 throttle 中间件,别自己重写——它底层已基于 Redis ZSET 实现滑动窗口,支持防穿透、动态 key、数据库后备,手撸容易掉进并发竞争、时间窗口漂移、跨服务器不一致这些坑。
throttle:60,1 为什么在 web 路由里完全没反应?
因为 throttle 中间件本身只是个“壳”,不绑定策略就不起作用。它默认只对 api 路由组生效,且依赖 RouteServiceProvider::configureRateLimiting() 里注册的命名策略。
- 你在
routes/web.php里写->middleware('throttle:60,1'),走的是匿名策略,但 Laravel 默认没为 web 组启用缓存限流(CACHE_DRIVER若是array或file,多进程下会静默失效) - 你在
routes/api.php里写->middleware('throttle:api'),却没在RouteServiceProvider中定义RateLimiter::for('api', ...),就会报错 “Rate limiter [api] is not defined” - 检查
config/cache.php的'default'是否指向真实 Redis store(比如redis),而不是cache.store配置和实际中间件用的 store 不一致
怎么按用户角色差异化限流(VIP/游客/普通登录用户)?
不能靠中间件参数硬编码,必须在 RateLimiter::for() 的闭包里动态判断,且 by() 返回值要稳定一致,否则同个用户被算成多个桶。
- 游客用 IP:但 CDN 后所有请求共享一个公网 IP,得改用请求头里的
X-Forwarded-For或设备指纹 - 登录用户统一用
$request->user()?->id,别混用session_id()(API 场景 session 可能未启动) - VIP 用户配额更高,但 key 仍用
$request->user()->id,避免 VIP 和普通用户共用一个计数器 - 示例策略(放在
RouteServiceProvider::configureRateLimiting()):RateLimiter::for('api', function (Request $request) { $key = $request->user()?->id ?? 'guest_'.($request->ip() ?: 'unknown'); if ($request->user()?->isVip()) { return Limit::perMinute(500)->by($key); } return $request->user() ? Limit::perMinute(100)->by($key) : Limit::perMinute(10)->by($key); });
reset_in=3600 到底管不管用?
reset_in 只控制响应头 X-RateLimit-Reset 的值,**不影响实际限流逻辑**。真正决定窗口期的是 perMinute() 这类方法背后的存储策略(Redis ZSET 滑动窗口),不是这个参数。
- 设
throttle:60,1,reset_in=86400,前端看到“24 小时后重置”,但后端其实每分钟都在滚动统计——体验断层,别这么干 - 建议保持
reset_in和速率单位一致:throttle:60,1,reset_in=60 - 别用它做业务分支,比如“重置后发短信”,要用
RateLimitingAttempted事件或独立定时任务清理 key
最常被忽略的点:限流 key 的构造。用 by=id 在 JWT 场景下可能因 token 过期导致旧 ID 还在计数器里占坑;用 IP 在移动网络下易误杀(NAT 共享 IP)。真实项目里,by() 返回值必须是你能稳定提取、且业务上“一人一桶”的标识,比如设备指纹 + 用户 ID 的组合,而不是图省事抄文档里的默认写法。











