167 lines
5.7 KiB
PHP
167 lines
5.7 KiB
PHP
<?php
|
||
namespace App\Controllers\Admin;
|
||
|
||
use App\Controllers\Controller;
|
||
use Core\App;
|
||
use Core\Db;
|
||
use Core\Installer;
|
||
|
||
/**
|
||
* 后台:数据库升级
|
||
* - 将升级包(*.sql)放入 install/upgrades/ 目录后,本页自动提示「可升级」。
|
||
* - 支持单个升级与一键全部升级;每次执行写入 db_upgrades 记录(文件名 / 哈希 / 时间 / 操作人)。
|
||
* - 内容发生变更的已升级包会被重新标记为「需更新」。
|
||
* 路由:/admin/upgrade -> 升级面板
|
||
* /admin/upgrade/apply/<file.sql> -> 升级单个
|
||
* /admin/upgrade/applyall -> 一键全部升级
|
||
* /admin/upgrade/init -> 基础数据/表结构初始化(POST,仅超管)
|
||
*/
|
||
class UpgradeController extends Controller
|
||
{
|
||
protected $layout = 'layouts/admin';
|
||
|
||
private function dir(): string
|
||
{
|
||
return BASE_PATH . '/install/upgrades';
|
||
}
|
||
|
||
public function index()
|
||
{
|
||
admin_required();
|
||
if (Db::driver() !== 'mysql') {
|
||
return $this->view('admin/upgrade', ['driver' => 'file']);
|
||
}
|
||
$this->requireSuper();
|
||
|
||
Installer::ensureUpgradeLog();
|
||
$files = $this->scan();
|
||
$applied = $this->appliedMap();
|
||
|
||
$list = [];
|
||
foreach ($files as $f) {
|
||
$name = basename($f);
|
||
$hash = md5_file($f);
|
||
$state = !isset($applied[$name]) ? 'pending'
|
||
: ($applied[$name] !== $hash ? 'changed' : 'done');
|
||
$list[] = [
|
||
'name' => $name,
|
||
'size' => filesize($f),
|
||
'mtime' => filemtime($f),
|
||
'state' => $state,
|
||
];
|
||
}
|
||
// 排序:待升级 / 已变更 在前,已应用在后
|
||
$rank = ['pending' => 0, 'changed' => 1, 'done' => 2];
|
||
usort($list, fn($a, $b) => ($rank[$a['state']] ?? 9) - ($rank[$b['state']] ?? 9));
|
||
|
||
$pending = count(array_filter($list, fn($x) => $x['state'] !== 'done'));
|
||
$history = Db::query("SELECT * FROM db_upgrades ORDER BY applied_at DESC, id DESC LIMIT 50")->fetchAll();
|
||
|
||
return $this->view('admin/upgrade', [
|
||
'driver' => 'mysql',
|
||
'list' => $list,
|
||
'pending' => $pending,
|
||
'history' => $history,
|
||
]);
|
||
}
|
||
|
||
/** 升级单个升级包 */
|
||
public function apply($file = null)
|
||
{
|
||
admin_required();
|
||
$this->requireSuper();
|
||
$file = basename((string)$file);
|
||
$path = $this->dir() . '/' . $file;
|
||
if (!is_file($path) || !preg_match('/\.sql$/i', $file)) {
|
||
$this->flash('升级包不存在', 'err');
|
||
$this->redirect('admin/upgrade');
|
||
return '';
|
||
}
|
||
try {
|
||
Installer::applySqlFile($path);
|
||
Installer::ensureUpgradeLog();
|
||
Db::query(
|
||
"INSERT INTO db_upgrades (file, hash, applied_at, applied_by, note) VALUES (?, ?, ?, ?, ?)",
|
||
[$file, md5_file($path), date('Y-m-d H:i:s'), ($_SESSION['admin']['username'] ?? 'admin'), '']
|
||
);
|
||
$this->flash("已升级:{$file}", 'ok');
|
||
} catch (\Throwable $e) {
|
||
$this->flash('升级失败:' . $e->getMessage(), 'err');
|
||
}
|
||
$this->redirect('admin/upgrade');
|
||
return '';
|
||
}
|
||
|
||
/** 一键升级全部待处理 */
|
||
public function applyAll()
|
||
{
|
||
admin_required();
|
||
$this->requireSuper();
|
||
$files = $this->scan();
|
||
$applied = $this->appliedMap();
|
||
$done = 0;
|
||
foreach ($files as $f) {
|
||
$name = basename($f);
|
||
$hash = md5_file($f);
|
||
if (isset($applied[$name]) && $applied[$name] === $hash) continue;
|
||
try {
|
||
Installer::applySqlFile($f);
|
||
Installer::ensureUpgradeLog();
|
||
Db::query(
|
||
"INSERT INTO db_upgrades (file, hash, applied_at, applied_by, note) VALUES (?, ?, ?, ?, ?)",
|
||
[$name, $hash, date('Y-m-d H:i:s'), ($_SESSION['admin']['username'] ?? 'admin'), '']
|
||
);
|
||
$done++;
|
||
} catch (\Throwable $e) {
|
||
$this->flash('升级失败:' . e($e->getMessage()), 'err');
|
||
$this->redirect('admin/upgrade');
|
||
return '';
|
||
}
|
||
}
|
||
$this->flash($done > 0 ? "已批量升级 {$done} 个升级包" : '没有需要升级的包', $done > 0 ? 'ok' : 'err');
|
||
$this->redirect('admin/upgrade');
|
||
return '';
|
||
}
|
||
|
||
/** 基础数据/表结构初始化(保留旧版能力:补齐新模块表与种子) */
|
||
public function init()
|
||
{
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $this->redirect('admin/upgrade'); return ''; }
|
||
admin_required();
|
||
$this->requireSuper();
|
||
if (!csrf_check()) {
|
||
$this->flash('表单已过期,请刷新后重试', 'err');
|
||
$this->redirect('admin/upgrade');
|
||
return '';
|
||
}
|
||
$msgs = Installer::upgrade();
|
||
$this->flash('基础数据升级完成 ✓ ' . implode(';', $msgs), 'ok');
|
||
$this->redirect('admin/upgrade');
|
||
return '';
|
||
}
|
||
|
||
/* ---------------- 工具 ---------------- */
|
||
|
||
private function requireSuper(): void
|
||
{
|
||
if (!is_admin() || admin_role() !== 'super_admin') {
|
||
App::forbidden('仅超级管理员可执行数据库升级');
|
||
}
|
||
}
|
||
|
||
private function scan(): array
|
||
{
|
||
$d = $this->dir();
|
||
return is_dir($d) ? (glob($d . '/*.sql') ?: []) : [];
|
||
}
|
||
|
||
private function appliedMap(): array
|
||
{
|
||
try {
|
||
return Db::query("SELECT file, hash FROM db_upgrades")->fetchAll(\PDO::FETCH_KEY_PAIR);
|
||
} catch (\Throwable $e) {
|
||
return [];
|
||
}
|
||
}
|
||
}
|