104 lines
3.4 KiB
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;
|
|
}
|
|
}
|