mysql建表语句为:create table tp_request_log (id int(11) unsigned not null auto_increment, ip varchar(45) not null default '', url varchar(255) not null default '', method varchar(10) not null default '', status_code smallint(3) not null default 200, cost_ms int(11) not null default 0, create_time int(11) not null default 0, primary key (id), key idx_ip_time (ip, create_time), key idx_url_time (url, create_time)) engine=innodb default charset=utf8mb4;字段精简,仅含时间、ip、url、方法、状态码、耗时,满足基础请求统计需求。

直接上能跑的代码,不加抽象层、不套管理后台模板,只做请求统计展示——核心就是记录 $_SERVER 和 $_REQUEST,存进数据库再查出来渲染表格。
怎么建表(MySQL)
用最简字段:时间、IP、URL、方法、状态码、耗时。不需要日志级别或用户ID这类扩展字段,后期要加再改。
CREATE TABLE `tp_request_log` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(45) NOT NULL DEFAULT '', `url` varchar(255) NOT NULL DEFAULT '', `method` varchar(10) NOT NULL DEFAULT '', `status_code` smallint(3) NOT NULL DEFAULT 200, `cost_ms` int(11) NOT NULL DEFAULT 0, `create_time` int(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_ip_time` (`ip`, `create_time`), KEY `idx_url_time` (`url`, `create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
中间件里记日志(ThinkPHP 6.1+)
别在控制器里手动写,中间件才是正解。注意两点:一是必须在 after 阶段读响应状态和耗时;二是避免记录静态资源(如 .js、.css)干扰统计。
- 新建
app/middleware/RequestLogger.php - 在
app/middleware.php中注册为全局中间件:'app\middleware\RequestLogger' - 关键逻辑:用
$response->getStatusCode()拿状态码,用microtime(true)算耗时 - 过滤掉
favicon.ico和常见后缀:in_array(pathinfo($request->url(), PATHINFO_EXTENSION), ['js', 'css', 'png', 'jpg', 'gif'])
示例片段(仅核心逻辑):
public function handle($request, \Closure $next)
{
$startTime = microtime(true);
$response = $next($request);
$endTime = microtime(true);
<pre class="brush:php;toolbar:false;">$url = $request->url();
$ext = pathinfo($url, PATHINFO_EXTENSION);
if (in_array($ext, ['js', 'css', 'png', 'jpg', 'gif', 'ico']) || $url === '/favicon.ico') {
return $response;
}
$logData = [
'ip' => $request->ip(),
'url' => $url,
'method' => $request->method(),
'status_code' => $response->getStatusCode(),
'cost_ms' => (int)(($endTime - $startTime) * 1000),
'create_time' => time(),
];
try {
Db::name('request_log')->insert($logData);
} catch (\Exception $e) {
// 忽略写库失败,不影响主流程
}
return $response;}
后台控制器输出 HTML 表格(无 JS 分页)
不引入 LayUI 或 Vue,就用原生 PHP + Bootstrap 4 的 table 样式,方便嵌入现有后台。重点是分页和时间筛选——别用 limit 1000 直接查全量,会卡死。
- 默认查最近 100 条,按
create_time DESC排序 - 支持 GET 参数
?page=2&start=2024-01-01&end=2024-01-31 - 时间范围用
strtotime()转成时间戳,再查create_time BETWEEN ? AND ? - 每页 20 条,用
Db::name('request_log')->page($page, 20)
控制器方法(app/controller/Admin/Log.php):
public function index()
{
$page = input('page/d', 1);
$start = input('start/s', '');
$end = input('end/s', '');
<pre class="brush:php;toolbar:false;">$where = [];
if ($start && $end) {
$startTime = strtotime($start);
$endTime = strtotime($end . ' 23:59:59');
if ($startTime && $endTime) {
$where['create_time'] = ['between', [$startTime, $endTime]];
}
}
$list = Db::name('request_log')
->where($where)
->order('id DESC')
->page($page, 20)
->select();
$count = Db::name('request_log')->where($where)->count();
$pages = ceil($count / 20);
return view('', compact('list', 'pages', 'page', 'start', 'end'));}
对应视图 view/admin/log/index.html 就一个带筛选表单和 <table> 的页面,用 <code>{$vo.url} 这类 ThinkTemplate 语法输出即可。
容易被忽略的三个点
不是所有请求都该记;不是所有环境都适合开这个中间件;不是所有数据库都能扛住高频写入。
- 开发环境建议关掉:在中间件开头加
if (App::isDebug()) return $response; - 高并发站点慎用:每请求一次就 insert 一条,QPS 过 500 就要考虑异步写(比如发到 Redis 队列,另起脚本消费)
-
ip字段要用varchar(45),否则 IPv6 存不下;url字段长度不够会截断,255 是底线
真要长期用,记得加定时清理任务(比如只保留最近 30 天),不然表体积涨得比想象中快。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











