不能直接用cache::inc()做频次统计,因并发下会丢计数,且key生成不规范(如ip、路径未标准化)或未加时间窗口切片会导致统计失效;应使用redis::increx或incr+expire配合标准化key(含时间戳、规范化ip和路由规则)在中间件中实现安全限流。

为什么不能直接用 Cache::inc() 做频次统计
因为并发场景下会丢计数。ThinkPHP 的 Cache::inc() 底层多数走的是文件或 Redis 的 INCR,看似原子,但前提是 key 的生成逻辑必须严格隔离——比如用户 IP + 接口路径拼接时没做标准化(含 query 参数顺序不同、大小写、编码差异),就会导致同一请求被记成多个 key;更常见的是没加时间窗口切片,把“1 分钟内请求次数”存成一个永久 key,根本没法清理。
实操建议:
- 所有统计 key 必须带时间戳前缀,例如
rate:2024052014:192.168.1.1:/api/user/info,小时级切片既可控又省空间 - 避免用
$_SERVER['REMOTE_ADDR']直取 IP,要过request()->ip()并启用trust_client_ip配置,否则 Nginx 反代后全是 127.0.0.1 - 接口路径统一用
request()->rule()而非request()->url(),排除动态参数干扰
如何在中间件里安全拦截超频请求
ThinkPHP 6 的中间件是唯一能稳定介入请求生命周期早期的位置,且支持中断响应。别在控制器里做判断——那时路由已匹配、数据库可能已查、日志已写,拦截成本高还容易漏。
实操建议:
- 中间件
handle()方法开头就调用频次检查,返回json(['code'=>429, 'msg'=>'请求过于频繁'])并return $next($request)不继续执行 - 使用
Redis::incrEx($key, 1, 3600)一步完成自增+过期(注意:TP6.1+ 才支持incrEx,低版本需拆成incr+expire两步并加 try-catch) - 阈值判断后,立即用
Redis::ttl($key)补过期时间,防止因并发导致 key 意外永不过期
RateLimitMiddleware 实例代码关键片段
以下不是完整类,只列易错和必改点:
<?php // app/middleware/RateLimitMiddleware.php
use think\facade\Cache;
use think\facade\Redis;
class RateLimitMiddleware
{
public function handle($request, \Closure $next)
{
$ip = $request->ip();
$rule = $request->rule(); // 如 'api/user/info'
$window = date('YmdH'); // 小时粒度
$key = "rate:{$window}:{$ip}:{$rule}";
// 注意:这里必须用 Redis,Cache 门面可能走 file 驱动,不支持原子 incr
$count = Redis::incr($key);
if ($count == 1) {
Redis::expire($key, 3600); // 首次写入才设过期,避免重复 expire 冲突
}
if ($count > 100) { // 每小时最多 100 次
return json(['code' => 429, 'msg' => '请求过于频繁'], 429);
}
return $next($request);
}
}
注意:Redis::incr() 返回的是整型,不是字符串;如果 Redis 连接失败,incr 会抛异常,线上务必包一层 try/catch 返回默认放行,别让限流逻辑拖垮整个服务。
上线前必须验证的三个边界
本地测通不等于线上可用。这三个点一漏,监控就失真、告警就乱报:
- 用
curl -H "X-Forwarded-For: 1.1.1.1" http://yoursite.com/api/test模拟反代,确认中间件拿到的是真实客户端 IP,不是 Nginx 的$remote_addr - 对带参数的接口如
/api/list?page=2&size=10和/api/list?size=10&page=2发请求,看是否命中同一个 key(应由rule()保证,而非url()) - 手动用
redis-cli查keys rate:2024052014:*,确认 key 数量合理(IP × 接口数 × 小时数),若爆炸增长,说明 key 拼接逻辑有变量未归一化
时间窗口粒度越小,key 越多,但精度越高;选小时还是分钟,得看业务容忍延迟和 Redis 内存预算——这个权衡点,没人能替你按需算清楚。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











