需在请求生命周期内完成计数器的读取、递增与持久化,方法包括:一、Redis原子计数;二、数据库表持久化;三、模型Count按日聚合;四、中间件+文件日志;五、令牌绑定配额统计。

如果您在ThinkPHP项目中需要精确追踪每个API接口被调用的频次,则需在请求生命周期内完成计数器的读取、递增与持久化。以下是多种可落地的统计方法:
一、基于Redis缓存的原子计数
利用Redis的INCR命令实现线程安全的自增操作,避免数据库锁竞争,适用于高并发场景。Key设计应包含接口路径与可选维度(如用户ID、IP),且不嵌入时间戳,由TTL控制有效期。
1、在控制器或中间件中构造唯一键名,例如api:count:/v1/user/profile或api:count:user_456:/v1/order/list。
2、调用Cache::inc($key, 1, 3600)进行原子递增,第三个参数设定缓存过期时间为1小时。
立即学习“PHP免费学习笔记(深入)”;
3、若需定时落库,可配置命令行任务每小时执行一次,遍历匹配api:count:*的键,批量写入数据库并清空缓存。
二、数据库表驱动的持久化统计
通过独立数据表记录每次调用,保障数据不丢失,适合审计级需求。需注意写入性能,建议使用异步队列或批量插入优化。
1、创建数据表api_call_log,字段包括id(主键)、api_path(VARCHAR 255)、created_at(DATETIME)、ip(VARCHAR 45)。
2、在全局中间件ApiCallLogger的handle方法中,获取当前请求路径与客户端IP。
3、执行Db::name('api_call_log')->insert(['api_path' => $path, 'ip' => $ip, 'created_at' => date('Y-m-d H:i:s')])。
4、为提升性能,可将插入操作推入think-queue消息队列,由后台消费者进程统一处理。
三、使用模型Count方法按日聚合查询
当统计目标为每日调用量而非实时频次时,可复用ThinkPHP内置聚合能力,避免额外存储开销。该方式依赖日志表已存在且含时间字段。
1、确保日志表api_call_log中created_at字段为标准DATETIME类型。
2、编写统计逻辑:$todayCount = Db::name('api_call_log')->whereTime('created_at', 'today')->count();
3、如需按接口路径分组统计当日调用次数:$grouped = Db::name('api_call_log')->whereTime('created_at', 'today')->group('api_path')->field('api_path,count(*) as count')->select();
4、返回结果中count字段为字符串类型,前端展示前必须转换为整型:intval($item['count'])。
四、中间件+文件追加的日志轻量方案
适用于开发环境或低流量站点,无需数据库或Redis依赖,直接以文本格式记录每次调用基础信息,便于快速排查。
1、在app/middleware/ApiFileLogger.php中编写handle方法。
2、拼接日志行:"" . date('Y-m-d H:i:s') . "\t" . $request->url() . "\t" . $request->ip() . "\n"。
3、使用file_put_contents(runtime/log/api_calls.log, $line, FILE_APPEND | LOCK_EX)追加写入。
4、设置Logrotate或定时脚本每日切割日志,防止单文件过大;解析时可用grep -c "/v1/user" runtime/log/api_calls.log粗略统计。
五、令牌绑定的调用配额联动统计
若系统已接入新版ThinkAPI服务并启用令牌机制,则调用次数天然与Token强关联,可直接复用其后台统计数据,避免重复开发。
1、确认接口调用头中携带Authorization: Bearer {token}或X-API-Token: {token}。
2、在网关层或前置中间件中提取$token = $request->header('authorization', '')->replace('Bearer ', '')。
3、将$token与当前$request->url()组合为维度键,调用Cache::inc("token:call:{$token}:{$path}", 1, 86400)。
4、该键值可用于校验是否超出配额,例如if (Cache::get("token:call:{$token}:{$path}") > 1000) { throw new HttpException(429); },其中1000为该Token对该接口的日限额。



















