最稳埋点位置是中间件(app/middleware/requeststat.php),因其能覆盖所有http请求,包括未登录用户、options预检及404路由,且可在handle()中准确获取响应状态码与耗时,避免app::after()事件执行时机不可靠的问题。

接口请求统计该记录哪些字段
只记 url、method、status_code、response_time、client_ip 和 create_time 就够用。不需要存完整请求体或响应体——既拖慢写入,又带来安全与存储隐患。
常见错误是把 $_POST 或 $request->param() 全部序列化进数据库,结果单条日志几百 KB,几天就撑爆表;还有人存 user_agent 却不截断,遇到 Chrome 的长 UA 直接触发 MySQL text 字段长度警告。
-
url建议用varchar(255),并统一去除 query 参数(如用parse_url($url, PHP_URL_PATH)提取路径) -
status_code用tinyint unsigned,避免存成字符串导致后续无法GROUP BY status_code >= 400 -
response_time存毫秒整数(int),别存"123.45ms"这种字符串
在 ThinkPHP 中哪里埋点最稳
用中间件(app/middleware/RequestStat.php)比在控制器里手动调 Db::insert() 可靠得多——它能覆盖所有 HTTP 请求,包括未登录用户、OPTIONS 预检、甚至 404 路由未匹配的请求(只要走完路由层)。
别在 app/common.php 或全局 __construct() 里写,那些地方拿不到最终响应状态码和耗时;也别依赖 App::after() 事件,TP6.1+ 中该事件不保证在响应发送前执行,可能写入失败却无感知。
- 中间件
handle()方法开头记录microtime(true)到$request->attr('start_time') - 在
return $next($request)->withHeader(...)之后补一句日志写入,确保$response->getStatusCode()可读 - 务必用
try...catch包裹写入逻辑,防止日志失败拖垮主流程
怎么避免高并发下写库拖慢接口
直接同步写 MySQL,在 QPS > 50 时就会明显拖慢接口——不是因为 SQL 慢,而是磁盘 I/O 和连接池争抢。必须异步化。
最轻量的解法是用 ThinkPHP 自带的 queue + sync 驱动(非 Redis):把日志数据推到内存队列,由定时任务或独立脚本消费。不引入新服务,也不需要改部署结构。
- 中间件中用
think\facade\Queue::push('app\job\RequestStatJob', $data)推送 - 创建
app/job/RequestStatJob.php,fire()方法里做批量插入(例如每 50 条或 1 秒 flush 一次) - 禁用
log驱动的write日志,否则queue自身日志会和统计日志互相干扰
查统计报表时为什么 COUNT 很慢
当表数据过百万,SELECT COUNT(*) FROM request_stat WHERE create_time > '2024-01-01' 会扫全表——即使有 create_time 索引,MySQL 仍可能因统计精度放弃索引。
根本解法不是加索引,而是换聚合方式:每天零点用 Cron 执行一次汇总,把结果存到 request_stat_daily 表里。查询时只查这张小表。
- 汇总 SQL 示例:
INSERT INTO request_stat_daily (date, total, success, fail, avg_time) SELECT CURDATE(), COUNT(*), SUM(IF(status_code - 原表
request_stat加复合索引:(create_time, status_code),避免单字段索引失效 - 别用
EXPLAIN看执行计划就以为没问题——实际跑起来才发现Using filesort或临时表膨胀
真正难的不是写入库逻辑,而是决定“哪些请求不该记”:健康检查接口(/api/health)、静态资源(.js、.css)、CORS 预检(OPTIONS)都得过滤掉,否则统计失真。这个规则要写死在中间件的白名单判断里,而不是靠后期 SQL WHERE 过滤。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











