v1.0.51: 批次1架构筑基 - 1)版本迁移(common/migrate.php+schema_versions+natsort迁移文件);2)登录安全(bcrypt平滑迁移login/user_add/user_update旧sha1命中自动重写+system_login_attempts限流+session加固httponly/samesite/secure);3)CSRF Token(服务端checkCsrf+auth/csrf.php登录页取token+前端common.js/login.js统一携带X-CSRF-Token);4)统一基座common/Api.php并存量强制迁移全部70个endpoint(Api::boot按public/super/module/permissions分流,写接口强制checkAjax+checkCsrf,全局异常处理);5)前端收敛(common.js新增esc转义别名);6)REV-4硬数据来源渠道可编辑(hard_update+补全弹窗下拉);7)prepared卫生(soft_update.php修复+analysis.php表名白名单REV-9)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
qianqiuwanzi
2026-09-17 14:48:17 +08:00
parent 69fd4246c2
commit c86f08c325
91 changed files with 1072 additions and 463 deletions
+136
View File
@@ -0,0 +1,136 @@
<?php
/**
* 数据库迁移执行器(批次1「迁移版本管理」基座)
*
* 用法:
* php tools/migrate.php # 等同 up
* php tools/migrate.php up # 顺序执行 sql/migration_*.sql 中未应用的迁移
* php tools/migrate.php status # 列出已应用 / 待应用
* php tools/migrate.php down <version> # 移除 v<=version 的版本记录(仅记录,SQL 不自动回滚)
*
* 规则:
* - 迁移文件命名 sql/migration_v1.0.XX.sql,按版本号升序执行(natsort)
* - 版本表 schema_versions 由 sql/schema_versions.sql 引导建表
* - 每个迁移记录 version + 文件 md5(checksum),已应用文件被改动会告警并跳过
* - 只支持正向迁移(down 仅为运维便利,SQL 变更需手工回滚)
*/
// 命令行运行保护
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "[错误] 请通过命令行运行:php tools/migrate.php\n");
exit(1);
}
$cfg = require __DIR__ . '/../config/database.php';
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=%s', $cfg['host'], $cfg['port'], $cfg['dbname'], $cfg['charset']);
try {
$pdo = new PDO($dsn, $cfg['username'], $cfg['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
fwrite(STDERR, "[错误] 数据库连接失败:{$e->getMessage()}\n");
exit(1);
}
$sqlDir = __DIR__ . '/../sql';
$schemaSql = file_get_contents($sqlDir . '/schema_versions.sql');
/** 建版本表(不存在才建) */
function ensureSchemaTable(PDO $pdo, $schemaSql)
{
try {
$pdo->query("SELECT COUNT(*) FROM schema_versions");
} catch (PDOException $e) {
$pdo->exec($schemaSql);
echo "[初始化] 已创建 schema_versions 表\n";
}
}
/** 读取已应用记录:version => checksum */
function appliedVersions(PDO $pdo)
{
$map = [];
foreach ($pdo->query("SELECT version, checksum FROM schema_versions")->fetchAll() as $r) {
$map[$r['version']] = $r['checksum'];
}
return $map;
}
/** 扫描迁移文件:返回 [version => absolutePath] 升序 */
function migrationFiles($sqlDir)
{
$files = glob($sqlDir . '/migration_*.sql');
natsort($files);
$map = [];
foreach ($files as $f) {
$base = basename($f, '.sql'); // migration_v1.0.51
$ver = substr($base, strlen('migration_')); // v1.0.51
$map[$ver] = $f;
}
return $map;
}
$action = $argv[1] ?? 'up';
switch ($action) {
case 'up':
ensureSchemaTable($pdo, $schemaSql);
$applied = appliedVersions($pdo);
$files = migrationFiles($sqlDir);
$pending = 0;
foreach ($files as $ver => $path) {
$sum = md5_file($path);
if (isset($applied[$ver])) {
if ($applied[$ver] !== $sum) {
fwrite(STDERR, "[警告] {$ver} 已应用但文件被改动(checksum 不一致),已跳过。如需重跑请先手动清理 schema_versions。\n");
}
continue;
}
$pending++;
echo "[迁移] 应用 {$ver} ... ";
try {
$pdo->exec(file_get_contents($path));
$ins = $pdo->prepare("INSERT INTO schema_versions (version, applied_at, checksum) VALUES (?, NOW(), ?)");
$ins->execute([$ver, $sum]);
echo "OK\n";
} catch (PDOException $e) {
fwrite(STDERR, "失败:{$e->getMessage()}\n");
exit(1);
}
}
if ($pending === 0) {
echo "数据库已是最新版本(共 " . count($applied) . " 个迁移)。\n";
} else {
echo "完成,本次应用 {$pending} 个迁移。\n";
}
break;
case 'status':
ensureSchemaTable($pdo, $schemaSql);
$applied = appliedVersions($pdo);
$files = migrationFiles($sqlDir);
echo str_pad('版本', 12) . str_pad('状态', 10) . "文件\n";
echo str_repeat('-', 60) . "\n";
foreach ($files as $ver => $path) {
$state = isset($applied[$ver]) ? '已应用' : '待应用';
printf("%-12s %-10s %s\n", $ver, $state, basename($path));
}
break;
case 'down':
$target = $argv[2] ?? '';
if ($target === '') {
fwrite(STDERR, "用法:php tools/migrate.php down <version>\n");
exit(1);
}
ensureSchemaTable($pdo, $schemaSql);
$del = $pdo->prepare("DELETE FROM schema_versions WHERE version >= ?");
$del->execute([$target]);
echo "已移除 version >= {$target} 的迁移记录(SQL 变更不会自动回滚,请自行处理)。\n";
break;
default:
fwrite(STDERR, "未知命令:{$action}(支持 up / status / down <version>)\n");
exit(1);
}