Files
coolcoth.com/app/Controllers/Admin/UpgradeController.php
T
2026-08-08 15:53:53 +08:00

167 lines
5.7 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
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 [];
}
}
}