OPTIONS预检请求不该统计,因其由浏览器自动发起、无业务参数和价值,统计会导致调用量虚高、Redis冗余、数据库脏数据及限流误判;应在中间件中通过$request->isOptions()提前跳过所有统计逻辑。

ThinkPHP 不该统计 OPTIONS 预检请求——它不是真实业务接口,统计它只会污染调用量数据、误导限流策略、拉低指标可信度。
为什么 OPTIONS 请求不该进统计逻辑
OPTIONS 是浏览器自动发的探测请求,不携带业务参数、不触发控制器、不产生业务价值。把它和 /api/v1/user 一起计数,会导致:
- 同一用户点一次按钮,
/api/v1/user调用次数显示为 2(1 次 OPTIONS + 1 次 POST) - Redis 原子计数器被无效占用,高并发下 key 冗余暴涨
- 数据库日志表里塞满
api_path = "OPTIONS"的脏数据,查WHERE api_path = '/api/v1/user'时必须额外过滤 - 前端轮询限流数据时,看到
count: 42,实际有效请求可能只有 21 次
在中间件中主动跳过 OPTIONS 统计
所有统计逻辑(Redis 自增、DB 插入、日志写入)必须放在 $request->isOptions() 判断之后。以全局日志中间件为例:
app/middleware/ApiCallLogger.php
<?php
namespace app\middleware;
use think\facade\Cache;
use think\facade\Db;
use think\Request;
class ApiCallLogger
{
public function handle(Request $request, \Closure $next)
{
// ✅ 第一步:如果是 OPTIONS,直接放行,不记录、不递增、不走后续统计
if ($request->isOptions()) {
return $next($request);
}
// ✅ 第二步:只对真实请求做统计
$path = $request->url(true); // 或 $request->rule()->getRule() 标准化路由
$ip = $request->ip();
// Redis 计数(推荐)
$key = 'api:count:' . md5($path) . ':' . $ip;
Cache::inc($key, 1, 3600);
// 数据库日志(异步更佳)
Db::name('api_call_log')->insert([
'api_path' => $path,
'ip' => $ip,
'created_at'=> date('Y-m-d H:i:s')
]);
return $next($request);
}
}
关键点:
立即学习“PHP免费学习笔记(深入)”;
-
$request->isOptions()必须放在最开头,不能等$next($request)执行完再判断 - 不要用
$request->method() === 'OPTIONS',ThinkPHP 封装了更稳的isOptions() - 若用了
throttle中间件,它内部也应跳过 OPTIONS —— 查看源码可见其默认已处理,但自定义限流逻辑里仍要手动加判断
如何验证 OPTIONS 真没被统计
用 curl 直接发预检请求,观察后端行为是否“静默”:
curl -X OPTIONS \ -H "Origin: https://admin.example.com" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type,Authorization" \ http://your.app/api/v1/user -v
预期结果:
- 响应状态码是
204或200,不是405 - 响应头含
Access-Control-Allow-Origin等 CORS 字段 - Redis 中查不到
api:count:*options*类似 key - 数据库
api_call_log表里没有api_path为OPTIONS或空字符串的记录
真正难的不是加一行 if ($request->isOptions()),而是整个团队对“预检请求非业务请求”这件事有共识——一旦有人在控制器里写 if ($request->method() === 'OPTIONS') { $this->record(); },统计就废了。



















