所有控制器继承统一基类再注入统计逻辑:新建app\basecontroller.php继承\think\controller,在initialize()中埋点统计,确保所有业务控制器均继承它,并通过$request->iscli()、路径白名单等过滤非http请求。

所有控制器继承统一基类再注入统计逻辑
ThinkPHP 默认每个控制器独立,跨控制器统计请求必须在入口或共用层拦截。最稳妥的方式是让所有控制器继承一个自定义基类,在基类的 initialize() 中统一埋点。这样既不侵入业务代码,又能覆盖全部 HTTP 请求(包括未显式调用 parent::initialize() 的情况,只要继承关系正确)。
常见错误是只在某个控制器里写统计逻辑,或试图在中间件里读取 $this->request —— 中间件中没有控制器上下文,$this 指向的是中间件实例,无法直接获取当前执行的控制器/操作名。
- 新建
app\BaseController.php,继承\think\Controller - 在
initialize()里调用统计方法,例如写入 Redis 或数据库 - 确保所有业务控制器都
extends BaseController,而非直接继承\think\Controller - 统计前先判断是否为真实 HTTP 请求(排除命令行、定时任务等),可用
$this->request->isSsl()或$this->request->isGet()等组合判断
用中间件 + Request 对象提取控制器与操作信息
中间件本身无法直接访问控制器实例,但可以通过 $request->controller() 和 $request->action() 获取路由解析后的目标控制器名和方法名。前提是路由已正确解析,即中间件需注册在「路由之后、控制器执行之前」的位置(如 app/middleware.php 中放在 think\middleware\LoadLangPack 之后)。
容易踩的坑是把中间件注册在太早的位置(比如 BeforeRequest),此时 $request->controller() 返回空字符串或默认值,导致统计丢失。
- 中间件中使用
$request->controller()和$request->action()获取目标标识 - 注意大小写:返回值是驼峰格式(如
UserLogin、index),非文件名(Userlogin.php) - 若启用了多应用模式,还需通过
$request->app()补充应用名 - 避免在中间件里做耗时操作(如同步写数据库),建议推送到队列或用
fastcgi_finish_request()异步落库
统计数据结构设计要考虑路由变量和参数差异
单纯记录 controller/action 不足以反映真实请求分布。例如 Article/read?id=123 和 Article/read?id=456 会归为同一项,但实际可能是热点文章与冷门文章混在一起。需要区分「模板维度」和「实例维度」。
推荐做法是:统计时保留原始 URL 路径(不含 query 参数),同时单独记录关键参数(如 id、type)的出现频次。这样既能看接口调用量,也能快速定位高频 ID。
- 用
$request->url(true)获取带参数的完整路径,再用parse_url()提取 path 部分用于聚合 - 对敏感参数(如
token、sign)需过滤,避免污染统计维度 - Redis 存储建议用 Hash:
HINCRBY stats:controller:Article:read id_123 1,便于按条件查 TopN - 数据库表字段至少包含:
controller、action、method、path_md5(防长路径)、create_time
忽略 CLI 请求和健康检查接口
如果不加过滤,定时任务、Shell 命令、K8s liveness probe(如 GET /ping)都会被计入,导致统计数据失真。ThinkPHP 的 $request->isCli() 可识别命令行,但无法区分探针请求——这类请求通常路径固定、无 Cookie、User-Agent 为空或含 curl/health 字样。
更可靠的做法是结合路径白名单 + 请求特征双重判断。不要依赖单一条件,否则容易漏掉或误杀。
- 明确列出无需统计的路径,如
/ping、/healthz、/api/v1/debug - 检查
$request->header('User-Agent')是否为空或匹配^curl|^Go-http-client|^kube-probe - 跳过
$request->isOptions()请求(CORS 预检),它们不携带业务逻辑 - 在统计前加
if ($this->shouldSkipStat()) { return; }封装判断逻辑,方便复用
$request->controller() 为空,还被当成默认控制器记了一笔。这种问题必须在预发环境用真实探针流量验证。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











