tp8.0项目需用滑动窗口限流防御恶意刷量,前提为启用redis扩展、配置可连通的redis连接、注册限流中间件;核心采用zset+lua脚本原子实现,按场景设置差异化key与窗口策略,并通过ab压测及日志验证生效。

接口被恶意刷量导致服务器响应变慢、短信验证码被批量盗取、支付接口被反复试探,这些问题在TP8.0项目中必须立刻拦截——固定窗口限流在窗口切换瞬间允许双倍请求通过,根本挡不住有准备的攻击者。
确认滑动窗口限流的适用前提
第一步:检查当前TP8.0环境是否已启用Redis扩展。执行 php -m | grep redis,若无输出则需先安装 phpredis 扩展并重启PHP-FPM服务。
第二步:确认Redis连接配置已写入 config/cache.php 中的 redis 驱动项,且 host、port、password(如有)全部可连通。【未验证Redis连通性就写Lua脚本,会导致限流逻辑完全不生效】
第三步:确保目标接口已注册为中间件路由,例如在 app/middleware.php 中绑定 App\Middleware\RateLimitMiddleware::class 到对应路由组。
用ZSET实现滑动窗口计数(核心方案)
方法一:基于时间戳分片的ZSET存储
在Redis中为每个用户/IP生成唯一key,如 rate:login:192.168.1.100;每次请求时,以毫秒级时间戳为score,随机字符串为member,执行 ZADD key now randstr;再用 ZREMRANGEBYSCORE key 0 (now-1000) 清理1秒前的数据;最后 ZCARD key 获取当前窗口请求数。
方法二:使用Lua脚本原子执行(推荐)
将以下脚本保存为 sliding_window.lua:
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
local key = KEYS[1]local window_ms = tonumber(ARGV[1])local max_count = tonumber(ARGV[2])local now = tonumber(ARGV[3])redis.call("ZREMRANGEBYSCORE", key, 0, now - window_ms)local count = redis.call("ZCARD", key)if count <br><code> redis.call("ZADD", key, now, tostring(now) .. ":" .. math.random(1000,9999)) redis.call("PEXPIRE", key, window_ms + 1000) return 1else return 0end
在TP8.0中间件中调用:$this->redis->eval(file_get_contents('sliding_window.lua'), [$key, 1000, 5, microtime(true)*1000]) —— 这里1000表示1秒窗口,5是阈值,单位毫秒时间戳保证精度。
按场景配置不同限流策略
登录接口:IP+手机号组合key,窗口10秒、阈值10次,防止暴力撞库。注意手机号需脱敏拼接,避免明文泄露风险。
短信发送接口:手机号单维度key,窗口60秒、阈值2次,同时叠加图形验证码前置校验。
支付回调接口:商户号+订单号复合key,窗口300秒、阈值1次,防重放攻击——【此处必须开启幂等性校验,仅限流不能替代业务层去重】
验证限流是否生效
用ab命令压测:ab -n 20 -c 10 "http://yourdomain.com/api/login",观察返回状态码分布。正常应出现部分429响应。
手动检查Redis:redis-cli zrange rate:login:127.0.0.1 0 -1 WITHSCORES,确认ZSET中只有最近1秒内的成员。
查看TP8.0日志:runtime/log/202607/07_rate_limit.log,搜索关键词“rejected_by_sliding_window”,确认拦截记录持续写入。










