feat(nginx-log): 多节点适配(node_id 全链路 + 全局/按节点聚合 + 节点筛选 + v2.4.0 升级脚本)

- 6 张表加 node_id 字段 + position/stat 改复合唯一键(migration)
- 6 个 Scheme 同步 node_id 注解 + 唯一键
- Reader 带 node_id 参数 + 懒加载接管(getLastPosition 回退查 node_id='')
- Aggregator 全局行(node_id='')+ 按节点循环 insertAggregatesForScope
- Stat 所有查询方法加 where node_id 条件
- Dashboard 控制器/视图加节点筛选下拉框 + AJAX 带 node_id
- AccessLog 列表加节点列 + 采集进度卡片显示节点信息
- import 定时任务 run_type 改 all(每节点采集自己的日志)
- v2.4.0 升级脚本追加 ALTER TABLE(幂等 check-then-alter)
- 菜单调整:去掉 Nginx 顶级菜单 + 读取位置管理,改为系统管理下挂两个子菜单
- ulthon-timer 文档补充 run_type=all 多节点部署注意事项
This commit is contained in:
augushong
2026-08-08 10:33:20 +08:00
parent f1a2e8fd5d
commit 8faa52ce90
21 changed files with 578 additions and 206 deletions

View File

@@ -45,11 +45,13 @@ class NginxLogStatBase extends AdminController
$period = 'day';
}
$nodeId = (string) $request->param('node_id', '');
// 解析时间范围YYYYMMDD int 表示)
[$startDate, $endDate] = $this->resolveDateRange($period);
if ($request->isAjax()) {
$data = $this->buildStatData($period, $startDate, $endDate);
$data = $this->buildStatData($period, $startDate, $endDate, $nodeId);
return json([
'code' => 0,
@@ -63,6 +65,11 @@ class NginxLogStatBase extends AdminController
$this->assign('start', $startDate);
$this->assign('end', $endDate);
// 在线节点列表(供视图渲染节点筛选下拉)
$nodes = \app\admin\model\SystemHost::where('status', 1)->order('node_id', 'asc')->select();
$this->assign('nodes', $nodes);
$this->assign('node_id', $nodeId);
return $this->fetch();
}
@@ -140,20 +147,20 @@ class NginxLogStatBase extends AdminController
/**
* 组装完整统计数据
*/
protected function buildStatData(string $period, int $startDate, int $endDate): array
protected function buildStatData(string $period, int $startDate, int $endDate, string $nodeId = ''): array
{
// 时间范围不合法 → 空数据(不崩)
if ($startDate <= 0 || $endDate <= 0 || $startDate > $endDate) {
return $this->emptyData($period);
}
$summary = $this->querySummary($startDate, $endDate);
$trend = $this->queryTrend($period, $startDate, $endDate);
$summary = $this->querySummary($startDate, $endDate, $nodeId);
$trend = $this->queryTrend($period, $startDate, $endDate, $nodeId);
$topN = $this->getTopN();
$topUrls = $this->queryTopByDateRange(NginxStatUrl::class, 'uri', $startDate, $endDate, $topN);
$topReferers = $this->queryTopByDateRange(NginxStatReferer::class, 'referer_domain', $startDate, $endDate, $topN);
$topUas = $this->queryTopByDateRange(NginxStatUa::class, 'ua_name', $startDate, $endDate, $topN);
$topUrls = $this->queryTopByDateRange(NginxStatUrl::class, 'uri', $startDate, $endDate, $topN, $nodeId);
$topReferers = $this->queryTopByDateRange(NginxStatReferer::class, 'referer_domain', $startDate, $endDate, $topN, $nodeId);
$topUas = $this->queryTopByDateRange(NginxStatUa::class, 'ua_name', $startDate, $endDate, $topN, $nodeId);
// 跨周期 UV 估算标注:仅 day 为精确值week/month/year/custom 均为按日累加的估算值
$uvIsEstimate = $period !== 'day';
@@ -208,7 +215,7 @@ class NginxLogStatBase extends AdminController
* 整体指标SUM stat_hour
* avg_request_time 按 PV 加权平均(避免简单 AVG 被低 PV 时段拉偏)
*/
protected function querySummary(int $startDate, int $endDate): array
protected function querySummary(int $startDate, int $endDate, string $nodeId = ''): array
{
$sumFields = [
'IFNULL(SUM(pv),0) AS pv',
@@ -223,6 +230,7 @@ class NginxLogStatBase extends AdminController
];
$row = NginxStatHour::where('stat_date', 'between', [$startDate, $endDate])
->where('node_id', $nodeId)
->field(implode(',', $sumFields))
->find();
@@ -252,10 +260,11 @@ class NginxLogStatBase extends AdminController
* - year → 12 个月(按 stat_date DIV 100 分组,补全 1~12 月)
* - custom→ 按 stat_date 分组,补全 start~end 日期序列
*/
protected function queryTrend(string $period, int $startDate, int $endDate): array
protected function queryTrend(string $period, int $startDate, int $endDate, string $nodeId = ''): array
{
if ($period === 'day') {
$rows = NginxStatHour::where('stat_date', 'between', [$startDate, $endDate])
->where('node_id', $nodeId)
->field('stat_hour, IFNULL(SUM(pv),0) AS pv, IFNULL(SUM(uv),0) AS uv')
->group('stat_hour')
->select()
@@ -280,6 +289,7 @@ class NginxLogStatBase extends AdminController
if ($period === 'year') {
$rows = NginxStatHour::where('stat_date', 'between', [$startDate, $endDate])
->where('node_id', $nodeId)
->field('(stat_date DIV 100) AS ym, IFNULL(SUM(pv),0) AS pv, IFNULL(SUM(uv),0) AS uv')
->group('ym')
->order('ym asc')
@@ -307,6 +317,7 @@ class NginxLogStatBase extends AdminController
// week / month / custom → 按 stat_date 分组,补全日期序列
$rows = NginxStatHour::where('stat_date', 'between', [$startDate, $endDate])
->where('node_id', $nodeId)
->field('stat_date, IFNULL(SUM(pv),0) AS pv, IFNULL(SUM(uv),0) AS uv')
->group('stat_date')
->order('stat_date asc')
@@ -340,10 +351,12 @@ class NginxLogStatBase extends AdminController
* @param string $modelClass 模型类(依赖倒置:调 app/admin/model 入口)
* @param string $labelField 分组与展示的字段名
* @param int $topN 取前 N 条0 不限制
* @param string $nodeId 节点 ID空字符串=全局聚合行)
*/
protected function queryTopByDateRange(string $modelClass, string $labelField, int $startDate, int $endDate, int $topN): array
protected function queryTopByDateRange(string $modelClass, string $labelField, int $startDate, int $endDate, int $topN, string $nodeId = ''): array
{
$query = $modelClass::where('stat_date', 'between', [$startDate, $endDate])
->where('node_id', $nodeId)
->field($labelField . ' AS label, IFNULL(SUM(pv),0) AS pv, IFNULL(SUM(uv),0) AS uv')
->group($labelField)
->order('pv desc');