必须在全局中间件或应用初始化阶段介入,推荐在app/middleware.php注册RequestCounterMiddleware;Redis是唯一合理选择,用INCR命令配合归一化URL与日期分片key实现高并发计数。

接口请求次数统计该挂在哪里
必须在全局中间件或应用初始化阶段介入,不能只靠控制器里写计数逻辑——否则漏掉 404、中间件拦截、异常中断等情况。ThinkPHP 6+ 推荐用 app/middleware.php 注册一个全局中间件,比如 RequestCounterMiddleware;TP5.1 则优先放在 app/common.php 的 hook::add('action_begin', ...) 或自定义行为中。
用 Redis 还是数据库存计数
Redis 是唯一合理选择。数据库写入太重,高并发下容易成为瓶颈,且接口统计本身不要求强持久性。用 INCR 命令最直接,key 设计建议为:api:count:{md5($request->url())}:{date('Ymd')},这样既能按天分片,又避免 URL 中带参数导致 key 泛滥。别用 $request->url() 原样拼,要先 normalize:去掉 query string 中的 token、timestamp 等动态参数,否则同个接口会散成几十个 key。
- 错误示例:
api:count:/api/user/info?id=123&ts=1712345678→ 每次请求 ts 不同,key 就不同 - 正确做法:正则过滤掉
ts=.*?&?、sign=.*?&?等无意义参数后再 md5 - TP6 可直接用
cache()->redis()->incr($key),注意设置过期时间(如EXPIRE $key 86400)
如何避免重复计数和漏计数
核心是绕过 ThinkPHP 的「路由未匹配」和「异常终止」两种情况。中间件要在 think\Response 构造前就执行计数,且必须用 try...finally 包裹 incr 操作——哪怕后续抛出 HttpException 或 500,计数也要落地。另外,静态资源(.js/.css/.png)和健康检查接口(如 /ping)建议白名单过滤,否则日志和监控接口自己刷爆计数。
- 在中间件
handle()开头就做 URL 归一化 + key 生成 - 在
finally块里执行cache()->redis()->incr($key) - 加判断:
if (in_array($path, ['/static/', '/favicon.ico', '/ping'], true)) return $next($request); - 别依赖
$response->getCode()来判断是否成功——404、401 也是有效请求,该计还得计
查询统计结果时要注意时间精度
Redis 本身不支持范围查询,所以按天统计得靠 key 命名规则配合 KEYS api:count:*:20240405(不推荐,线上禁用),更稳妥的是把每日汇总值在零点写入一个独立 key,比如 api:summary:20240405,用 HSET 存各接口路径的次数。这样查某天 top10 接口只要 HGETALL + array_multisort 即可。如果要用 MySQL 做归档,也只应每天凌晨从 Redis 导一次,而不是每请求都写库。
立即学习“PHP免费学习笔记(深入)”;
真正难处理的是「实时分钟级统计」——这时候得引入 INCRBY 配合 key 命名为 api:minute:202404051423:/api/order/list,但 key 数量爆炸,务必加 TTL(如 3600 秒),否则 Redis 内存撑不住。



















