文件还在测试中
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
<?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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user