CaiWuXiTong/app/Console/Commands/ReencryptUpstreamPasswords.php

104 lines
3.4 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Utilities\SystemDatabaseConfiguration;
use App\Utilities\UpstreamCredentialCipher;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use RuntimeException;
use Throwable;
class ReencryptUpstreamPasswords extends Command
{
protected $signature = 'system-database:reencrypt-upstream-passwords {--apply : 备份后执行重加密}';
protected $description = '将旧 APP_KEY 云数据库密文迁移为可跨开发机共享的密文';
public function handle(
SystemDatabaseConfiguration $configuration,
UpstreamCredentialCipher $cipher
): int {
if ($configuration->current()['mode'] !== 'shared') {
$this->components->error('当前不是共享系统数据库,拒绝执行。');
return self::FAILURE;
}
$rows = DB::table('system_database_connections')
->whereNotNull('password')
->where(function ($query) {
$query->whereNull('shared_password')->orWhere('shared_password', '');
})
->get(['id', 'source_key', 'password', 'shared_password']);
if ($rows->isEmpty()) {
$this->components->info('没有需要迁移的共享云数据库密码。');
return self::SUCCESS;
}
if (! $this->option('apply')) {
$this->components->warn(sprintf(
'检测到 %d 条旧密码记录。当前仅检查,使用 --apply 才会备份并迁移。',
$rows->count()
));
return self::SUCCESS;
}
try {
$backupPath = $this->backup($rows->all());
DB::transaction(function () use ($rows, $cipher) {
foreach ($rows as $row) {
$plain = Crypt::decryptString((string) $row->password);
DB::table('system_database_connections')
->where('id', $row->id)
->update([
'shared_password' => $cipher->encrypt($plain),
'updated_at' => now(),
]);
}
});
} catch (Throwable $exception) {
$this->components->error(
'迁移失败,未提交任何数据库修改。请确认当前环境仍持有旧记录对应的 APP_KEY。'
);
return self::FAILURE;
}
$this->components->info(sprintf(
'已备份并迁移 %d 条记录。私有备份:%s',
$rows->count(),
$backupPath
));
return self::SUCCESS;
}
private function backup(array $rows): string
{
$directory = storage_path('app/system-database-config-backups');
if (! is_dir($directory) && ! mkdir($directory, 0700, true) && ! is_dir($directory)) {
throw new RuntimeException('无法创建私有备份目录。');
}
$path = $directory . DIRECTORY_SEPARATOR
. 'upstream-credentials-' . now()->format('Ymd-His-u') . '.json';
$payload = json_encode($rows, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
if (file_put_contents($path, $payload, LOCK_EX) === false) {
throw new RuntimeException('无法写入私有备份。');
}
chmod($path, 0600);
return $path;
}
}