Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

在 Laravel 12 中實作資料庫備份與還原,最穩定且常見的做法是封裝 mysqldump 與 mysql 指令。我們將結合 Spatie Activitylog 記錄每一次的操作,並透過 Scheduler 實現自動化。

php artisan make:model DatabaseBackup -mLaravel 會產生 xxx_xx_xx_xxxxx_create_database_backups_table.php & Model:DatabaseBackup.php
public function up(): void
{
Schema::create('database_backups', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->string('path');
$table->bigInteger('size'); // 位元組
$table->string('type'); // 'manual' 或 'scheduled'
$table->timestamps();
});
}建立 app/Services/BackupService.php 來處理繁瑣的 shell 指令。
namespace App\Services;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use App\Models\DatabaseBackup;
use Spatie\Activitylog\Models\Activity;
class BackupService
{
public function createBackup($type = 'manual')
{
$filename = "backup-" . now()->format('Y-m-d-H-i-s') . ".sql";
$path = storage_path("app/backups/{$filename}");
if (!file_exists(storage_path('app/backups'))) {
mkdir(storage_path('app/backups'), 0755, true);
}
// 取得資料庫配置
$config = config('database.connections.mysql');
// 執行 mysqldump
$process = Process::run("mysqldump -u {$config['username']} -p'{$config['password']}' {$config['database']} > {$path}");
if ($process->successful()) {
$backup = DatabaseBackup::create([
'filename' => $filename,
'path' => $path,
'size' => filesize($path),
'type' => $type
]);
activity('database')
->performedOn($backup)
->causedBy(auth()->user())
->withProperties(['size' => $backup->size])
->log("建立了資料庫備份: {$filename}");
return $backup;
}
throw new \Exception("備份失敗: " . $process->errorOutput());
}
public function restoreBackup($id)
{
$backup = DatabaseBackup::findOrFail($id);
$config = config('database.connections.mysql');
// 執行還原
$process = Process::run("mysql -u {$config['username']} -p'{$config['password']}' {$config['database']} < {$backup->path}");
if ($process->successful()) {
activity('database')
->performedOn($backup)
->causedBy(auth()->user())
->log("還原了資料庫版本: {$backup->filename}");
return true;
}
throw new \Exception("還原失敗: " . $process->errorOutput());
}
}建立 API 控制器:BackupController
php artisan make:controller --api API\BackupControllerController (app/Http/Controllers/API/BackupController.php):
public function store(BackupService $service) {
return response()->json($service->createBackup('manual'));
}
public function restore($id, BackupService $service) {
$service->restoreBackup($id);
return response()->json(['message' => '還原成功']);
}routes/console.php):use App\Services\BackupService;
use Illuminate\Support\Facades\Schedule;
// 每天凌晨 3 點自動備份
Schedule::call(function (BackupService $service) {
$service->createBackup('scheduled');
})->dailyAt('03:00');首先,建立一個 Job 來處理耗時的備份程序。
php artisan make:job ProcessDatabaseBackupJob編輯 app/Jobs/ProcessDatabaseBackupJob.php:
namespace App\Jobs;
use App\Services\BackupService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Spatie\Activitylog\Models\Activity;
class ProcessDatabaseBackupJob implements ShouldQueue
{
use Queueable;
public string $type, $process, $backup_id;
/**
* Create a new job instance.
*/
public function __construct($type = 'manual', $process = 'backup', $backup_id = '')
{
$this->type = $type; // manual / scheduled
$this->process = $process; // backup / restore
$this->backup_id = $backup_id; // only for restore
}
/**
* Execute the job.
*/
public function handle(BackupService $backupService): void
{
if ($this->process === 'backup') {
// 備份
$result = $backupService->createBackup($this->type);
} else if ($this->process === 'restore') {
// 還原
if (empty($this->backup_id)) {
throw new \Exception("還原時必須提供 backup_id");
}
$result = $backupService->restoreBackup($this->backup_id);
} else {
throw new \Exception("不支援的處理類型: {$this->process}");
}
}
}編輯 routes/console.php:
use App\Jobs\ProcessDatabaseBackup;
use Illuminate\Support\Facades\Schedule;
// 每天凌晨 3 點派發備份任務到隊列
Schedule::job(new ProcessDatabaseBackup('scheduled','backup',''))->dailyAt('03:00');mysqldump --ignore-table。這是最專業的做法,系統日誌與 Tokens 完全不受還原影響。