mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-30 12:45:32 +08:00
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:
@@ -9,6 +9,7 @@ use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_nginx_access_log', comment: 'nginx 访问日志明细(解析入库)')]
|
||||
#[Index(columns: ['node_id', 'time_local'], name: 'idx_node_time', type: 'NORMAL')]
|
||||
#[Index(columns: ['time_local'], name: 'idx_time_local', type: 'NORMAL')]
|
||||
#[Index(columns: ['remote_addr'], name: 'idx_remote_addr', type: 'NORMAL')]
|
||||
#[Index(columns: ['status'], name: 'idx_status', type: 'NORMAL')]
|
||||
@@ -19,6 +20,9 @@ class NginxAccessLog extends BaseScheme
|
||||
#[Field(type: 'bigint', nullable: false, unsigned: true, autoIncrement: true, primary: true, comment: '主键ID')]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'varchar', length: 64, nullable: false, default: '', comment: '节点ID(区分不同采集节点)')]
|
||||
public $node_id;
|
||||
|
||||
#[Field(type: 'varchar', length: 500, nullable: false, comment: '来源日志文件路径')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $file_path;
|
||||
|
||||
@@ -9,12 +9,15 @@ use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_nginx_log_position', comment: 'nginx 日志文件读取位置(增量读取状态)')]
|
||||
#[Index(columns: ['file_path'], name: 'uniq_file_path', type: 'UNIQUE')]
|
||||
#[Index(columns: ['node_id', 'file_path'], name: 'uniq_node_file', type: 'UNIQUE')]
|
||||
class NginxLogPosition extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'varchar', length: 64, nullable: false, default: '', comment: '节点ID(区分不同采集节点)')]
|
||||
public $node_id;
|
||||
|
||||
#[Field(type: 'varchar', length: 500, nullable: false, comment: '日志文件绝对路径')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $file_path;
|
||||
|
||||
@@ -8,13 +8,16 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'nginx_stat_hour', comment: 'nginx 按小时聚合统计')]
|
||||
#[Index(columns: ['stat_date', 'stat_hour'], name: 'uniq_date_hour', type: 'UNIQUE')]
|
||||
#[Index(columns: ['node_id', 'stat_date', 'stat_hour'], name: 'uniq_node_date_hour', type: 'UNIQUE')]
|
||||
#[Index(columns: ['stat_date'], name: 'idx_stat_date', type: 'NORMAL')]
|
||||
class NginxStatHour extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'varchar', length: 64, nullable: false, default: '', comment: '节点ID(区分不同采集节点)')]
|
||||
public $node_id;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, comment: '统计日期(YYYYMMDD)')]
|
||||
public $stat_date;
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'nginx_stat_referer', comment: 'nginx 按 Referer 来源域名聚合统计')]
|
||||
#[Index(columns: ['stat_date', 'referer_domain'], name: 'uniq_date_referer', type: 'UNIQUE')]
|
||||
#[Index(columns: ['node_id', 'stat_date', 'referer_domain'], name: 'uniq_node_date_referer', type: 'UNIQUE')]
|
||||
#[Index(columns: ['stat_date'], name: 'idx_stat_date', type: 'NORMAL')]
|
||||
class NginxStatReferer extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'varchar', length: 64, nullable: false, default: '', comment: '节点ID(区分不同采集节点)')]
|
||||
public $node_id;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, comment: '统计日期(YYYYMMDD)')]
|
||||
public $stat_date;
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'nginx_stat_ua', comment: 'nginx 按 User-Agent 聚合统计')]
|
||||
#[Index(columns: ['stat_date', 'ua_type', 'ua_name'], name: 'uniq_date_type_name', type: 'UNIQUE')]
|
||||
#[Index(columns: ['node_id', 'stat_date', 'ua_type', 'ua_name'], name: 'uniq_node_date_type_name', type: 'UNIQUE')]
|
||||
#[Index(columns: ['stat_date'], name: 'idx_stat_date', type: 'NORMAL')]
|
||||
class NginxStatUa extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'varchar', length: 64, nullable: false, default: '', comment: '节点ID(区分不同采集节点)')]
|
||||
public $node_id;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, comment: '统计日期(YYYYMMDD)')]
|
||||
public $stat_date;
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'nginx_stat_url', comment: 'nginx 按 URL 聚合统计')]
|
||||
#[Index(columns: ['stat_date', 'uri'], name: 'uniq_date_uri', type: 'UNIQUE')]
|
||||
#[Index(columns: ['node_id', 'stat_date', 'uri'], name: 'uniq_node_date_uri', type: 'UNIQUE')]
|
||||
#[Index(columns: ['stat_date'], name: 'idx_stat_date', type: 'NORMAL')]
|
||||
class NginxStatUrl extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'varchar', length: 64, nullable: false, default: '', comment: '节点ID(区分不同采集节点)')]
|
||||
public $node_id;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, comment: '统计日期(YYYYMMDD)')]
|
||||
public $stat_date;
|
||||
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main">
|
||||
{if condition="$positions->isEmpty()"}
|
||||
<div class="layui-card" style="margin-bottom: 10px;">
|
||||
<div class="layui-card-body" style="padding: 12px 15px;">
|
||||
<i class="layui-icon layui-icon-tips" style="color: #FFB800; font-size: 16px;"></i>
|
||||
<span style="color: #666;">暂无采集位置记录,请确认 nginx_log.enable 已开启且 timer 正在运行。</span>
|
||||
</div>
|
||||
</div>
|
||||
{else/}
|
||||
{volist name="positions" id="pos"}
|
||||
<div class="layui-card" style="margin-bottom: 10px;">
|
||||
<div class="layui-card-body" style="padding: 12px 15px;">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 24px; align-items: center; font-size: 13px;">
|
||||
<div>
|
||||
<span style="color: #999;">监控文件</span><br>
|
||||
<code style="font-size: 12px;">{$pos.file_path}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: #999;">节点</span><br>
|
||||
<strong>{$pos.node_id}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: #999;">已读取</span><br>
|
||||
<strong>{$pos.offset_human}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: #999;">最后采集</span><br>
|
||||
<span>{$pos.last_read_time_text}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: #999;">解析失败</span><br>
|
||||
{if condition="$pos.parse_fail_count > 0"}
|
||||
<span style="color: #FF5722; font-weight: bold;">{$pos.parse_fail_count} 次</span>
|
||||
{else/}
|
||||
<span style="color: #5FB878;">0 次</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/volist}
|
||||
{/if}
|
||||
|
||||
<table id="currentTable" class="layui-table layui-hide"
|
||||
data-auth-index="{:auth('nginx.access_log/index')}"
|
||||
data-auth-read="{:auth('nginx.access_log/read')}"
|
||||
|
||||
@@ -17,6 +17,7 @@ $(function(){
|
||||
else if (s >= 500) cls = 'layui-badge layui-bg-red';
|
||||
return '<span class="' + cls + '">' + d.status + '</span>';
|
||||
}},
|
||||
{field: 'node_id', title: '节点', width: 120, searchOp: '='},
|
||||
{field: 'remote_addr', title: '客户端IP', width: 140, searchOp: '%*%'},
|
||||
{field: 'request_time', title: '耗时(秒)', width: 100, search: 'number_limit', templet: function(d) {
|
||||
var t = parseFloat(d.request_time);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="layui-card-header detail-header">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-md9">
|
||||
<h2 class="detail-title">#{$row.id} {$title}</h2>
|
||||
<h2 class="detail-title">#{$row.id} {$title|default=''}</h2>
|
||||
<div class="detail-id">ID: {$row.id}</div>
|
||||
</div>
|
||||
<div class="layui-col-md3 text-right detail-actions">
|
||||
|
||||
Reference in New Issue
Block a user