thinkphp接口限流需显式配置redis、透传真实ip、调整key粒度及http方法,否则90%配置无效;throttle中间件必须绑定路由且仅对指定方法生效,未登录请求需ip兜底,redis原子操作须用lua脚本。

ThinkPHP 接口限流不是“开了中间件就自动生效”,不配 Redis、不透传真实 IP、不改 key 粒度,90% 的配置都等于没做。
throttle 中间件为什么绑了路由也不拦截
它根本不会自动触发——必须显式绑定到路由分组或闭包路由,且只对匹配的 HTTP 方法生效。
- 检查
app/route/app.php是否用了->middleware('throttle:60,1')这类写法,而不是仅在app/middleware.php里设'use_global_middleware' => true - 上传类接口(如
POST /api/upload)默认不被 throttle 拦截,因为它的visit_method默认只含['GET'];必须在config/throttle.php中显式改为'visit_method' => ['POST'] - 闭包路由(如
Route::post('/upload', function() {}))必须单独加->middleware(),否则中间件链压根不走 - 别依赖控制器注解(如
@Middleware),TP6 默认不启用注解中间件,需额外开启app.middleware_annotation
RateLimiter 组件返回 always false 或完全不拦截
它底层所有令牌桶状态都存在 Redis 里,没连上 Redis 就等于开关没打开。
- 确认
config/cache.php中'default' => 'redis'已设置,且config/redis.php的host、port、password、database全部填对(内网环境常漏配database) - 用
redis-cli -h {host} -p {port} -a {password}手动连上去,执行KEYS rate:*看有没有桶 key 写入 - phpredis 版本低于 4.3.0 会导致 Lua 补令牌脚本失败,建议升到 5.3.7+
- 缓存驱动若误配为
file,高并发下文件锁会让计数严重失真,生产必须切redis
key 粒度不合理导致限流失效或误杀
默认 key 是 $request->url(true) . $request->ip(),带完整查询参数和路径,结果每个带不同 id=123 的请求都算独立 key,完全起不到聚合限速作用。
- 重写
getCacheKey()方法,优先用$request->rule()->getName()截取路由名(如v1.users.index),避免 URL 参数污染 - 按用户限速时,key 里拼
user_id,但未登录请求必须 fallback 到清洗后的 IP,否则游客全被堵死 - key 中禁止出现
/、:、?,用str_replace(['/', ':', '?'], '_', $key)预处理,Redis 对 key 名字符敏感 - 命名空间必须明确,例如
rate:api:v1.users.index:192.168.1.100,否则不同接口或 IP 的计数会互相覆盖
INCR + EXPIRE 不是原子操作,高并发下必超卖
两个请求同时 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是秒数,不是分钟 - 别用
SETNX + INCR组合——它无法解决多个请求同时发现 key 不存在、同时 setnx 成功的竞态 - 首次
INCR返回 1 才设EXPIRE,避免重复设置干扰 TTL 计算
最易被忽略的是 Nginx 层 IP 透传和中间件注册顺序:X-Real-IP 没透传,$request->ip() 就是 127.0.0.1;RateLimiter 若注册在跨域中间件之前,CORS 预检请求就被拦死了;若在鉴权之后,恶意请求可能绕过限流直击业务逻辑。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











