Cache::inc()可实现高并发准确计数,但需规范key设计(ip+标准化路由)、设置固定TTL、在中间件中调用,并导出时精准查询、前后端统一转整型。

直接用 Cache::inc() 就能实现高并发下准确计数,不需要自己读-改-写,也不用数据库锁;但 key 设计错、类型没转、TTL 没设对,统计就全乱了。
Cache::inc() 必须配合标准化路由和固定 TTL
ThinkPHP 的 Cache::inc() 是原子操作,但前提是 key 要稳定、生命周期要可控。如果 key 里混入 query 参数或时间戳,/user?id=1 和 /user?id=2 就算两个接口;如果没设 TTL,计数会一直累积不清理。
-
$route必须去 query、统一大写:用request()->rule()->getRuleName()或手动parse_url($url, PHP_URL_PATH)+strtolower() - TTL 建议设为 60 秒(对应限流窗口)或 3600 秒(按小时聚合),不要依赖业务逻辑“手动清空”
- key 格式推荐:
api:count:'.$ip.':'.$route,IP 和路由两级区分,避免跨用户污染 - 首次调用
Cache::inc($key, 1, 60)会自动初始化为 1,不用先Cache::set($key, 0, 60)
中间件里写统计逻辑,别塞在控制器里
控制器容易漏调用、难复用,且无法覆盖 404 或异常请求;中间件在路由解析后、控制器执行前触发,能捕获所有合法入口。
- 新建中间件
App\Middleware\ApiCounter,handle()方法中获取$request->ip()和标准化后的$route - 跳过 OPTIONS 预检请求:
if ($request->method() === 'OPTIONS') return $next($request); - 忽略静态资源路径:
if (str_starts_with($route, '/static/') || str_ends_with($route, '.js')) return $next($request); - 调用
Cache::inc("api:count:{$ip}:{$route}", 1, 60)后直接放行,不阻塞响应
导出数据时单点查,别用 KEYS 扫描
Redis 的 KEYS api:count:* 在生产环境会阻塞主线程,尤其 key 数量上万时,CPU 直接拉满。前端轮询或后台导出必须走精准查询。
- 提供一个后台接口,接收
ip和route参数,只查Cache::get("api:count:{$ip}:{$route}") - 返回值强制转整型:
(int) Cache::get($key),否则 Redis 返回的是 string,ECharts 渲染会失败 - 如果需要批量查某 IP 下所有接口调用量,前端分页传 route 列表,后端循环单点查,比扫 keys 安全十倍
- 缓存未命中时返回 0,不要抛异常或返回 null,避免前端 JSON 解析中断
前端渲染图表前必须做类型转换
后端返回的 count 字段从 Redis 来全是字符串,ECharts 的 series.data 要求数值数组,不转就显示空白或报错。
- 后端接口统一 cast:
'count' => (int) $value,而不是依赖json_encode()自动转换 - 前端取数时显式转:
data: res.data.map(d => Number(d.count)),别信parseInt()对空字符串的处理 - 测试用
curl -v http://your.app/api/stat?ip=127.0.0.1&route=/api/user看原始响应,确认字段是数字类型 - 注意:TP6 的
return json()默认 Content-Type 正确,但若中间加了 echo 或 header,JSON 就会损坏
真正卡住人的不是代码写不出来,而是 key 设计没想清楚维度、缓存没设 TTL 导致数据滞留、前端拿到字符串当数字用——这三处一错,图表永远画不出来。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











