mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-31 05:05:33 +08:00
- 跑 admin:permission:nodes 扫描 138 个权限节点(17 个 nginx 相关节点)
- 17 个 nginx 节点 assign 给 role-id=1(写入 ul_system_auth_node 满足 plan 验收)
- curd 路径修复:菜单 href 由 system.nginx_access_log/index 改为 nginx.access_log/index(匹配 curd 生成的实际 URL,方案 B)
- SystemMenu.php / v2.4.0.php / DB ul_system_menu 三处同步(含 id=303 log_position)
- dashboard id=301 system.nginx_log_stat/index 保持不变(控制器确实在 system 模块)
- 端到端 QA 全通过:100 行日志 → import 74 行(静态过滤)→ aggregate stat_hour=1/url=6 → dashboard 数据完整
- 修复 T10 遗留 bug:cleanStatTables 用 Unix 时间戳当阈值,与 stat_date YYYYMMDD 不匹配导致误删
- threshold 从 strtotime('today')-days*86400 改为 (int) date('Ymd', strtotime('today')-days*86400)
- 验证:366 天前 stat 数据被删,今天 stat 数据保留
- 权限隔离:超管可访问,未登录被拦(code=40101 请先登录后台)
- Evidence: .omo/evidence/task-16-nginx-log-analytics-e2e/(15 个文件)
173 lines
5.7 KiB
PHP
173 lines
5.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace base\tools\controller\timer;
|
||
|
||
use app\common\controller\TimerController;
|
||
use think\facade\Db;
|
||
use think\facade\Log;
|
||
|
||
/**
|
||
* nginx 日志清理定时任务(Base 层).
|
||
*
|
||
* 清理范围:
|
||
* 1. raw 表 ul_nginx_access_log:按 time_local 早于 N 天前分批删除(每批 5000 行)
|
||
* 2. 4 张 stat 表 ul_nginx_stat_{hour,url,referer,ua}:按 stat_date 早于 N 天前删除
|
||
*
|
||
* 保留:
|
||
* - position 表 ul_nginx_log_position 永久不删(增量位置基准,删了会全量重读)
|
||
*
|
||
* 控制参数(sysconfig,可后台界面动态调整,0 = 永久跳过):
|
||
* - nginx_log.raw_retention_days 默认 7 raw 表保留天数
|
||
* - nginx_log.stat_retention_days 默认 365 stat 表保留天数
|
||
*
|
||
* 业务侧如需定制清理逻辑,重写 app/tools/controller/timer/NginxLogClean.php 对应方法即可拦截。
|
||
*/
|
||
class NginxLogCleanBase extends TimerController
|
||
{
|
||
/**
|
||
* 防刷间隔(秒).
|
||
* 仅做控制器侧防刷,不影响定时器侧 frequency 节流。
|
||
*/
|
||
protected $frequency = 600;
|
||
|
||
/**
|
||
* 并发上限 cap:单分片(id=0)。
|
||
*/
|
||
protected $concurrency = 1;
|
||
|
||
/**
|
||
* raw 表分批删除的批量大小(避免单条 DELETE 锁表太久)。
|
||
*/
|
||
protected const RAW_BATCH_SIZE = 5000;
|
||
|
||
/**
|
||
* 每批 DELETE 之间的微秒休眠(避免主从延迟、降低 DB 压力)。
|
||
*/
|
||
protected const RAW_BATCH_USLEEP = 100000;
|
||
|
||
/**
|
||
* 需要清理的 stat 表列表(不含表前缀,由 Db::name 自动补)。
|
||
* 各表独立按 stat_date 清理,不做级联。
|
||
*/
|
||
protected const STAT_TABLES = [
|
||
'nginx_stat_hour',
|
||
'nginx_stat_url',
|
||
'nginx_stat_referer',
|
||
'nginx_stat_ua',
|
||
];
|
||
|
||
public function do()
|
||
{
|
||
// 1. env 总开关校验(严格布尔判断,详见 notepad Task 2 #14)
|
||
if (env('nginx_log.enable') !== true) {
|
||
Log::debug('nginx_log disabled, skip clean');
|
||
return 'disabled';
|
||
}
|
||
|
||
// 2. 读 sysconfig(intval 强转:DB 值是 varchar,默认值是 int)
|
||
$rawDays = (int) sysconfig('nginx_log', 'raw_retention_days', 7);
|
||
$statDays = (int) sysconfig('nginx_log', 'stat_retention_days', 365);
|
||
|
||
$rawDeleted = 0;
|
||
$statDeleted = 0;
|
||
|
||
// 3. raw 表分批清理(rawDays > 0 才执行,0 = 永久跳过)
|
||
if ($rawDays > 0) {
|
||
$rawDeleted = $this->cleanRawTable($rawDays);
|
||
}
|
||
|
||
// 4. stat 表清理(statDays > 0 才执行,0 = 永久跳过)
|
||
if ($statDays > 0) {
|
||
$statDeleted = $this->cleanStatTables($statDays);
|
||
}
|
||
|
||
return json_encode([
|
||
'raw_deleted' => $rawDeleted,
|
||
'stat_deleted' => $statDeleted,
|
||
], JSON_UNESCAPED_UNICODE);
|
||
}
|
||
|
||
/**
|
||
* 分批清理 raw 表 ul_nginx_access_log.
|
||
*
|
||
* 时间阈值基于 time_local(nginx 记录的请求时间戳),不是 create_time(入库时间)。
|
||
* 每批 5000 行 + usleep 100ms,避免长事务锁表。
|
||
*
|
||
* @param int $rawDays 保留天数(>0)
|
||
* @return int 累计删除行数
|
||
*/
|
||
protected function cleanRawTable(int $rawDays): int
|
||
{
|
||
$threshold = time() - ($rawDays * 86400);
|
||
$total = 0;
|
||
|
||
Log::info("nginx_log_clean: raw 阈值 time_local<" . date('Y-m-d H:i:s', $threshold) . " ({$rawDays} 天)");
|
||
|
||
// 循环分批 DELETE:每批 limit 5000,直到该阈值区间内无残留
|
||
while (true) {
|
||
// Db::name 自动加表前缀;limit 保证单次 DELETE 行数可控
|
||
$deleted = Db::name('nginx_access_log')
|
||
->where('time_local', '<', $threshold)
|
||
->limit(self::RAW_BATCH_SIZE)
|
||
->delete();
|
||
|
||
if ($deleted <= 0) {
|
||
break;
|
||
}
|
||
|
||
$total += $deleted;
|
||
|
||
// 批次间休眠,降低 DB 压力(避免主从延迟)
|
||
if ($deleted === self::RAW_BATCH_SIZE) {
|
||
usleep(self::RAW_BATCH_USLEEP);
|
||
} else {
|
||
// 末批(< 5000)说明已清完该阈值区间
|
||
break;
|
||
}
|
||
}
|
||
|
||
Log::info("nginx_log_clean: raw 删除 {$total} 行");
|
||
|
||
return $total;
|
||
}
|
||
|
||
/**
|
||
* 清理 4 张 stat 表(按 stat_date).
|
||
*
|
||
* stat_date 是 YYYYMMDD 整数(如 20260728),不是 Unix 时间戳。
|
||
* - stat 表一行 = 一天的聚合(stat_hour 按小时拆,但 stat_date 仍是当天 YYYYMMDD)
|
||
* - 阈值用 (int) date('Ymd', strtotime('today') - statDays*86400)(同为 YYYYMMDD 格式)
|
||
* 保证整天边界,不会误删"今天聚合了 N 小时但今天整体还没过期"的数据
|
||
*
|
||
* 各表独立 DELETE,不做级联(如 stat_url 不依赖 stat_hour,可单独保留)。
|
||
*
|
||
* @param int $statDays 保留天数(>0)
|
||
* @return int 累计删除行数(4 张表合计)
|
||
*/
|
||
protected function cleanStatTables(int $statDays): int
|
||
{
|
||
// strtotime('today') 返回当天 0 点时间戳;转 YYYYMMDD 整数与 stat_date 对齐
|
||
$threshold = (int) date('Ymd', strtotime('today') - ($statDays * 86400));
|
||
$total = 0;
|
||
|
||
Log::info("nginx_log_clean: stat 阈值 stat_date<{$threshold} ({$statDays} 天)");
|
||
|
||
foreach (self::STAT_TABLES as $table) {
|
||
$deleted = Db::name($table)
|
||
->where('stat_date', '<', $threshold)
|
||
->delete();
|
||
$total += $deleted;
|
||
|
||
if ($deleted > 0) {
|
||
Log::info("nginx_log_clean: {$table} 删除 {$deleted} 行");
|
||
}
|
||
}
|
||
|
||
Log::info("nginx_log_clean: stat 合计删除 {$total} 行");
|
||
|
||
return $total;
|
||
}
|
||
}
|