thinkphp限流需四条件齐备:路由绑定控制器、ip解析可信、缓存驱动为redis、key不冲突;否则throttle中间件静默失效。

ThinkPHP中间件限制请求频率,不能只靠 throttle 中间件加个参数就完事——它默认不生效是常态,不是异常。真正起作用的前提是:路由绑定方式正确、IP 解析可信、缓存驱动为 Redis、key 不冲突,四者缺一不可。
throttle 中间件为什么加了却不限流
常见现象是 Network 面板里压根看不到 Retry-After 或 429 响应,请求照常通过。根本原因有三个:
-
throttle只对显式绑定控制器方法的路由生效,Route::get('api/user', function(){})或资源路由未指定@index时,中间件根本不会执行 - 缓存驱动仍是
file(config/cache.php中'default' => 'file'),而Cache::inc()在 file 驱动下恒返回false,导致限流逻辑静默跳过 - Nginx 反代后
$request->ip()返回127.0.0.1,所有请求被当成同一个 IP 计数;没配proxy_set_header X-Real-IP $remote_addr;,也没在 PHP 层 fallback 到可信头
必须改的三个配置点
绕过“看似配了实则无效”的陷阱,这三项必须手动确认并修正:
- 在
config/cache.php中把'default'明确设为'redis',并确保config/redis.php的host、port、auth、select全部可连通 - Nginx 配置里加上两行:
proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - 路由定义必须是控制器方法绑定形式,例如:
Route::get('api/user', 'Api/UserController@index'),不能是闭包或未指定动作的资源路由
自定义中间件里用 Redis::incrEx 安全计数
官方 throttle 默认 key 是 throttle:ip:123.123.123.123,不带路径、不带时间切片,多个接口共享计数器——业务上基本不可用。推荐自己写中间件,用原子命令控制精度:
- key 拼接规则必须含三要素:
rate:+date('Y-m-d H:i')(小时级窗口) +$request->ip()(经request()->ip()且开启trust_client_ip) +md5($request->path())(排除 query 干扰) - TP6.1+ 直接用
Redis::incrEx($key, 1, 3600)一步完成自增+过期;低版本需拆成Redis::incr($key)+Redis::expire($key, 3600),并用try-catch包裹 - 判断阈值后,立即调
Redis::ttl($key)补过期时间,防止并发导致 key 意外永不过期 - 别用
$request->rule()拼 key —— 闭包路由下为空字符串,所有闭包接口会共用一个计数器
测试时怎么验证 IP 和 key 是否真实有效
上线前不验证,等于没配。最简单的验证方式是用 curl 手动构造请求头,比等前端报错快得多:
- 先确认 Nginx 透传是否生效:
curl -H "X-Real-IP: 192.168.1.100" http://your.app/api/test,然后在中间件里dump($request->ip()),看输出是不是192.168.1.100 - 在中间件中临时加一行:
dump($key); die;,观察生成的 key 是否含预期 IP、路径哈希和时间戳,有没有出现127.0.0.1或空路径 - 用
redis-cli连上 Redis,执行keys "rate:*"查看实际写入的 key,再用get和ttl确认值和过期时间是否符合预期
真正难的从来不是“怎么写代码”,而是让 $request->ip() 取到真实值、让 Cache::inc() 在 Redis 上真正原子执行、让 key 在所有部署环节里保持一致——这些细节一旦漏掉一个,限流就形同虚设。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











