feat(nginx-log): NginxLogClean 定时任务 + timer config 注册(完成 3 个 nginx 任务)

This commit is contained in:
augushong
2026-07-28 06:00:15 +08:00
parent 13b1313e92
commit 8eaa672ed6
3 changed files with 208 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace app\tools\controller\timer;
use base\tools\controller\timer\NginxLogCleanBase;
/**
* nginx 日志清理定时任务App 入口类).
*
* 框架路由 /tools/timer.NginxLogClean/do 与 /tools/timer.NginxLogClean/execute
* 均通过本类入口实例化(依赖倒置:定时器框架/路由系统直接调本类,不调 Base
*
* 业务侧如需定制清理行为(如改批大小、加自定义过滤、改保留策略等),
* 重写本类对应方法即可拦截,无需改动 extend/base/。
*/
class NginxLogClean extends NginxLogCleanBase
{
}

View File

@@ -29,4 +29,20 @@ return [
'concurrency' => 1,
'run_type' => 'auto',
],
[
'name' => 'nginx_log_aggregate',
'type' => 'site',
'target' => '/tools/timer.NginxLogStatAggregate/do',
'frequency' => 3600,
'concurrency' => 1,
'run_type' => 'auto',
],
[
'name' => 'nginx_log_clean',
'type' => 'site',
'target' => '/tools/timer.NginxLogClean/do',
'frequency' => 86400,
'concurrency' => 1,
'run_type' => 'auto',
],
];

View File

@@ -0,0 +1,172 @@
<?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. 读 sysconfigintval 强转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_localnginx 记录的请求时间戳),不是 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 是 strtotime('today') 起算的当天 0 点时间戳,与 raw 表 time_local 不同:
* - stat 表一行 = 一天的聚合stat_hour 按小时拆,但 stat_date 仍是当天 0 点)
* - 阈值用 strtotime('today') - statDays*86400而非 time() - statDays*86400
* 保证整天边界,不会误删"今天聚合了 N 小时但今天整体还没过期"的数据
*
* 各表独立 DELETE不做级联如 stat_url 不依赖 stat_hour可单独保留
*
* @param int $statDays 保留天数(>0
* @return int 累计删除行数4 张表合计)
*/
protected function cleanStatTables(int $statDays): int
{
// strtotime('today') 返回当天 0 点时间戳(整数)
$threshold = strtotime('today') - ($statDays * 86400);
$total = 0;
Log::info("nginx_log_clean: stat 阈值 stat_date<" . date('Y-m-d', $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;
}
}