Files
ulthon_admin/extend/base/admin/service/adminUpdateCodeData/v2.4.0.php
augushong ad96755ff5 docs(permission): 统一校准权限节点措辞,消除更新节点误解
admin:permission:nodes 是查看型命令(实时扫描注解、不写库、改注解即生效),但文档/注释多处用一键更新/自动更新/生成等动作词描述,导致开发者系统性误以为它是更新权限节点命令。本次校准 8 个文件 10 处误导措辞,并在命令输出末尾追加机制提示。@NodeAnotation 拼写保持不变(框架故意少一个 t)。
2026-08-08 18:21:25 +08:00

267 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 TABLE6 张 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.0nginx_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.0nginx_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('更新代码完成');
}
}