mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-30 20:55:32 +08:00
admin:permission:nodes 是查看型命令(实时扫描注解、不写库、改注解即生效),但文档/注释多处用一键更新/自动更新/生成等动作词描述,导致开发者系统性误以为它是更新权限节点命令。本次校准 8 个文件 10 处误导措辞,并在命令输出末尾追加机制提示。@NodeAnotation 拼写保持不变(框架故意少一个 t)。
267 lines
11 KiB
PHP
267 lines
11 KiB
PHP
<?php
|
||
/**
|
||
* @internal-framework
|
||
*
|
||
* 此文件为框架内置功能
|
||
*
|
||
* 用途:框架版本更新代码(v2.4.0)- nginx_log 分析模块存量升级
|
||
* 维护者:框架维护者
|
||
*
|
||
* 变更:
|
||
* - system_config 新增 nginx_log 组 4 条运行参数配置
|
||
* raw_retention_days 原始日志保留天数(0=永久)
|
||
* stat_retention_days 统计聚合保留天数(0=永久)
|
||
* topn_default Top N 默认值
|
||
* exclude_static 是否排除静态资源访问
|
||
* - system_menu 新增 2 条菜单(挂到系统管理 pid=228 下)
|
||
* 301 访问统计仪表盘 system.nginx_log_stat/index
|
||
* 302 原始访问日志 nginx.access_log/index
|
||
* - ALTER TABLE:6 张 nginx_log 表新增 node_id 字段 + 唯一键前置 node_id(多节点采集支持)
|
||
* 与 migration 20260801125222_nginx_log_add_node_id.php 保持一致,
|
||
* 存量库通过 admin:update 幂等执行(check-then-alter)
|
||
*
|
||
* 设计说明:
|
||
* - 幂等 check-then-insert(先查询存在性,不存在才插入)
|
||
* - 不分配超管权限:AuthServiceBase::isSuperAdmin() 对 admin_id=1 直接返回 true,
|
||
* 为 auth_id=1 植入 system_auth_node 是冗余且无效
|
||
* - 不直接植入权限节点:节点由注解动态扫描生成(admin:permission:nodes 仅用于查看核对)
|
||
*
|
||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||
*/
|
||
|
||
use think\console\Input;
|
||
use think\console\Output;
|
||
use think\facade\Db;
|
||
|
||
class UpdateFunction
|
||
{
|
||
/**
|
||
* @var Input
|
||
*/
|
||
public $input;
|
||
|
||
/**
|
||
* @var Output
|
||
*/
|
||
public $output;
|
||
|
||
/**
|
||
* 待新增 sysconfig 配置(nginx_log 组).
|
||
* 字段顺序与 adminInitData/SystemConfig.php 一致:name/group/value/remark/sort.
|
||
*/
|
||
public $newConfigs = [
|
||
[
|
||
'name' => 'raw_retention_days',
|
||
'group' => 'nginx_log',
|
||
'value' => '7',
|
||
'remark' => '原始日志保留天数(0=永久)',
|
||
'sort' => 0,
|
||
],
|
||
[
|
||
'name' => 'stat_retention_days',
|
||
'group' => 'nginx_log',
|
||
'value' => '365',
|
||
'remark' => '统计聚合保留天数(0=永久)',
|
||
'sort' => 0,
|
||
],
|
||
[
|
||
'name' => 'topn_default',
|
||
'group' => 'nginx_log',
|
||
'value' => '10',
|
||
'remark' => 'Top N 默认值(热门 URL/Referer/UA)',
|
||
'sort' => 0,
|
||
],
|
||
[
|
||
'name' => 'exclude_static',
|
||
'group' => 'nginx_log',
|
||
'value' => '1',
|
||
'remark' => '是否排除静态资源访问(0=不排除,1=排除 /css/.js/.png 等扩展名)',
|
||
'sort' => 0,
|
||
],
|
||
];
|
||
|
||
/**
|
||
* 待新增菜单.
|
||
* 字段顺序与 adminInitData/SystemMenu.php 一致:id/pid/title/icon/href/params/target/sort/status.
|
||
* 挂到系统管理(pid=228)下,不创建独立顶级分组。
|
||
*/
|
||
public $newMenus = [
|
||
[
|
||
'id' => 301,
|
||
'pid' => 228,
|
||
'title' => '访问统计仪表盘',
|
||
'icon' => 'fa fa-line-chart',
|
||
'href' => 'system.nginx_log_stat/index',
|
||
'params' => '',
|
||
'target' => '_self',
|
||
'sort' => 0,
|
||
'status' => 1,
|
||
],
|
||
[
|
||
'id' => 302,
|
||
'pid' => 228,
|
||
'title' => '原始访问日志',
|
||
'icon' => 'fa fa-file-text',
|
||
'href' => 'nginx.access_log/index',
|
||
'params' => '',
|
||
'target' => '_self',
|
||
'sort' => 0,
|
||
'status' => 1,
|
||
],
|
||
];
|
||
|
||
public function update()
|
||
{
|
||
$this->output->writeln('更新代码');
|
||
|
||
$this->output->info('v2.4.0:nginx_log 分析模块存量升级(补齐 4 条 sysconfig + 2 条菜单)');
|
||
|
||
$configTable = config('database.connections.mysql.prefix', 'ul_') . 'system_config';
|
||
$menuTable = config('database.connections.mysql.prefix', 'ul_') . 'system_menu';
|
||
|
||
// 确认表存在(防御性检查,参照 v2.3.0.php 范式)
|
||
$tablesExist = Db::query(
|
||
"SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (?, ?)",
|
||
[$configTable, $menuTable]
|
||
);
|
||
if (count($tablesExist) < 2) {
|
||
$this->output->warning("表 {$configTable} 或 {$menuTable} 不存在,跳过数据补齐。请先执行 scheme:sync 或 migrate:run 初始化表结构。");
|
||
|
||
return;
|
||
}
|
||
|
||
$configAdded = 0;
|
||
$configSkipped = 0;
|
||
foreach ($this->newConfigs as $cfg) {
|
||
// 幂等:用 (group, name) 检查存在性
|
||
$exists = Db::name('system_config')
|
||
->where('group', $cfg['group'])
|
||
->where('name', $cfg['name'])
|
||
->find();
|
||
if ($exists) {
|
||
$this->output->writeln(" - 配置 {$cfg['group']}.{$cfg['name']} 已存在,跳过");
|
||
++$configSkipped;
|
||
continue;
|
||
}
|
||
|
||
Db::name('system_config')->insert($cfg);
|
||
$this->output->writeln(" - 新增配置 {$cfg['group']}.{$cfg['name']} 完成");
|
||
++$configAdded;
|
||
}
|
||
|
||
$menuAdded = 0;
|
||
$menuSkipped = 0;
|
||
foreach ($this->newMenus as $menu) {
|
||
// 幂等:用 id 检查存在性(顶级菜单 href 为空,不能用 href 去重)
|
||
$exists = Db::name('system_menu')
|
||
->where('id', $menu['id'])
|
||
->find();
|
||
if ($exists) {
|
||
$this->output->writeln(" - 菜单 id={$menu['id']} ({$menu['title']}) 已存在,跳过");
|
||
++$menuSkipped;
|
||
continue;
|
||
}
|
||
|
||
Db::name('system_menu')->insert($menu);
|
||
$this->output->writeln(" - 新增菜单 id={$menu['id']} ({$menu['title']}) 完成");
|
||
++$menuAdded;
|
||
}
|
||
|
||
$totalSkipped = $configSkipped + $menuSkipped;
|
||
$this->output->info("本次新增 {$configAdded} 条配置、{$menuAdded} 条菜单,跳过 {$totalSkipped} 条已存在");
|
||
|
||
// ==================== node_id 字段 + 唯一键变更(多节点采集支持)====================
|
||
// 与 migration 20260801125222_nginx_log_add_node_id.php 保持一致
|
||
// 6 张表全部新增 node_id varchar(64) default ''
|
||
// position / 4 张 stat 表的唯一键前置 node_id(多节点隔离,避免聚合 INSERT 触发 duplicate key)
|
||
// raw 表(nginx_access_log)加 node_id + time_local 普通索引,无唯一键变更
|
||
$this->output->info('v2.4.0:nginx_log 6 张表新增 node_id 字段与唯一键调整');
|
||
|
||
$prefix = config('database.connections.mysql.prefix', 'ul_');
|
||
|
||
// 表短名 => 索引变更配置
|
||
// drop_idx: 要删除的旧索引名(raw 表无)
|
||
// add_idx: 新索引 [name, cols, unique]
|
||
$nodeIdTables = [
|
||
'nginx_log_position' => [
|
||
'drop_idx' => 'uniq_file_path',
|
||
'add_idx' => ['name' => 'uniq_node_file', 'cols' => ['node_id', 'file_path'], 'unique' => true],
|
||
],
|
||
'nginx_access_log' => [
|
||
'add_idx' => ['name' => 'idx_node_time', 'cols' => ['node_id', 'time_local'], 'unique' => false],
|
||
],
|
||
'nginx_stat_hour' => [
|
||
'drop_idx' => 'uniq_date_hour',
|
||
'add_idx' => ['name' => 'uniq_node_date_hour', 'cols' => ['node_id', 'stat_date', 'stat_hour'], 'unique' => true],
|
||
],
|
||
'nginx_stat_url' => [
|
||
'drop_idx' => 'uniq_date_uri',
|
||
'add_idx' => ['name' => 'uniq_node_date_uri', 'cols' => ['node_id', 'stat_date', 'uri'], 'unique' => true],
|
||
],
|
||
'nginx_stat_referer' => [
|
||
'drop_idx' => 'uniq_date_referer',
|
||
'add_idx' => ['name' => 'uniq_node_date_referer', 'cols' => ['node_id', 'stat_date', 'referer_domain'], 'unique' => true],
|
||
],
|
||
'nginx_stat_ua' => [
|
||
'drop_idx' => 'uniq_date_type_name',
|
||
'add_idx' => ['name' => 'uniq_node_date_type_name', 'cols' => ['node_id', 'stat_date', 'ua_type', 'ua_name'], 'unique' => true],
|
||
],
|
||
];
|
||
|
||
$colAdded = 0;
|
||
$colSkipped = 0;
|
||
foreach ($nodeIdTables as $tableShort => $idxCfg) {
|
||
$fullTable = $prefix . $tableShort;
|
||
|
||
// 防御性检查:表是否存在(未运行 migration 时跳过,不报错)
|
||
$tableExists = Db::query(
|
||
"SELECT COUNT(*) AS cnt FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?",
|
||
[$fullTable]
|
||
);
|
||
if (empty($tableExists) || (int) $tableExists[0]['cnt'] === 0) {
|
||
$this->output->writeln(" - {$fullTable} 表不存在,跳过(请先执行 migrate:run)");
|
||
continue;
|
||
}
|
||
|
||
// 幂等 1:检查 node_id 字段是否存在
|
||
$colExists = Db::query(
|
||
"SELECT COUNT(*) AS cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'node_id'",
|
||
[$fullTable]
|
||
);
|
||
if (!empty($colExists) && (int) $colExists[0]['cnt'] > 0) {
|
||
$this->output->writeln(" - {$fullTable} node_id 字段已存在,跳过");
|
||
++$colSkipped;
|
||
} else {
|
||
Db::execute("ALTER TABLE `{$fullTable}` ADD COLUMN `node_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '采集节点 ID(空=全局汇总)'");
|
||
$this->output->writeln(" - {$fullTable} 新增 node_id 字段完成");
|
||
++$colAdded;
|
||
}
|
||
|
||
// 幂等 2:唯一键/索引变更(检查新索引是否存在)
|
||
$newIdx = $idxCfg['add_idx']['name'] ?? null;
|
||
if ($newIdx !== null) {
|
||
$idxExists = Db::query(
|
||
"SELECT COUNT(*) AS cnt FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?",
|
||
[$fullTable, $newIdx]
|
||
);
|
||
if (empty($idxExists) || (int) $idxExists[0]['cnt'] === 0) {
|
||
// 删除旧索引(position / stat 表有,raw 表无)
|
||
if (!empty($idxCfg['drop_idx'])) {
|
||
Db::execute("ALTER TABLE `{$fullTable}` DROP INDEX `{$idxCfg['drop_idx']}`");
|
||
}
|
||
// 新增新索引
|
||
$idxType = !empty($idxCfg['add_idx']['unique']) ? 'UNIQUE' : 'INDEX';
|
||
$colsStr = implode(', ', array_map(function ($c) {
|
||
return "`{$c}`";
|
||
}, $idxCfg['add_idx']['cols']));
|
||
Db::execute("ALTER TABLE `{$fullTable}` ADD {$idxType} `{$newIdx}` ({$colsStr})");
|
||
$this->output->writeln(" - {$fullTable} 索引调整:{$newIdx}");
|
||
}
|
||
}
|
||
}
|
||
|
||
$this->output->info("node_id 迁移完成(新增字段 {$colAdded} 表,跳过 {$colSkipped} 表已存在)");
|
||
$this->output->writeln('更新代码完成');
|
||
}
|
||
}
|