throttle中间件默认不生效,必须手动绑定路由、透传真实ip、使用redis原子操作并自定义键名;否则因ip误判、计数超卖或键冲突导致限流失效。

throttle 中间件在 ThinkPHP 6+ 中能直接生效,但默认配置下几乎必然失效——它不自动挂载、不校验真实 IP、不保证原子计数、也不区分接口粒度。想让它真正起作用,必须手动干预关键环节。
throttle 中间件为什么没反应?路由没绑就等于没写
ThinkPHP 的 throttle 是个“被动组件”,注册了 ≠ 启用了。它不会自动拦截所有请求,必须显式绑定到具体路由或分组。
- 检查
app/middleware.php是否启用了全局中间件:'use_global_middleware' => true;但这只影响全局链,不等于给 API 加了限流 - 更可靠的做法是在路由定义里绑定:
Route::group('api', function () { /* ... */ })->middleware('throttle:60,1'); - 如果用闭包路由(如
Route::get('/user', function(){})),必须单独加->middleware(),否则中间件根本不会执行 - 常见错误:只在控制器方法上加注解
@Middleware,但 TP6 的注解中间件默认不启用,需额外配置app.middleware_annotation
真实 IP 拿不到,限流就全乱套
$request->ip() 在 Nginx + PHP-FPM 架构下大概率返回 127.0.0.1 或代理内网地址,导致所有用户共享一个计数器,或整个 CDN 网段被误封。
- Nginx 配置必须透传真实 IP:
proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - 在中间件中清洗 IP:
$ip = $request->header('x-real-ip') ?: ($request->header('x-forwarded-for') ? explode(',', $request->header('x-forwarded-for'))[0] : $request->ip());,并确保该 IP 是公网地址(排除10.*、192.168.*、127.0.0.1) - 别改
think\Request::ip()方法本身——升级时会被覆盖;优先用中间件预处理或继承重写Request类
Redis 计数超卖?INCR + EXPIRE 不是原子操作
用 $redis->incr($key); $redis->expire($key, 60) 是高并发下的经典陷阱:两个请求同时 INCR 返回 1,只有一个能成功 EXPIRE,另一个 key 永不过期,后续所有请求都算在同一个窗口里。
- 必须用 Lua 脚本封装原子逻辑:
local current = redis.call('incr', KEYS[1]) if current == 1 then redis.call('expire', KEYS[1], ARGV[1]) end return current - 调用时传入参数:
$this->redis->eval($lua, [$key], 1, 60),其中60是秒数,不是分钟 - 键名必须带命名空间,例如
rate:api:v1.users.index:192.168.1.100,避免不同接口或用户互相污染 - 如果用的是
topthink/think-throttle包,默认底层走 Cache 驱动,要确认config/cache.php中default已设为redis,否则会退化成文件缓存,压测即失效
按用户限流而不是 IP?重写 getLimitedId() 才是正解
默认 throttle 只认 IP,但业务常需「登录用户 ID」或「token 解析出的 user_id」做维度。硬编码 $_SESSION['user_id'] 或 $request->param('token') 不安全且不可复用。
- 新建中间件类,如
app\http\middleware\UserRateLimit,继承think\middleware\RateLimit - 重写
getLimitedId()方法,从请求头取Authorization或X-User-ID,或调用$request->userId()(需提前在认证中间件里注入) - 在路由中使用新中间件:
->middleware('app\http\middleware\UserRateLimit:10,0.5') - 注意:若 token 是 JWT,解析必须轻量(只验 signature + exp),别在限流层做完整鉴权,否则拖慢整个链路
Redis 键的 TTL 设置和 Lua 脚本的写法细节,比限流阈值本身更容易出问题。线上看到“明明设了 10 次/分钟,却 3 秒就封禁”,八成是过期时间算错、IP 没清洗干净,或者 key 冲突了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











