diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 0280cee8..8d762c24 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,6 +13,7 @@ use Illuminate\Console\Scheduling\Event; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Support\Facades\Schema; +use Nexus\Database\NexusDB; class Kernel extends ConsoleKernel { @@ -50,8 +51,8 @@ class Kernel extends ConsoleKernel $schedule->job(new ThirdPartyJob())->everyMinute()->withoutOverlapping(); $schedule->job(new MaintainPluginState())->everyMinute()->withoutOverlapping(); $schedule->job(new UpdateIsSeedBoxFromUserRecordsCache())->everySixHours()->withoutOverlapping(); + $schedule->job(new CheckCleanup())->everyFifteenMinutes()->withoutOverlapping(); - $this->registerScheduleCleanup($schedule); } /** @@ -66,17 +67,4 @@ class Kernel extends ConsoleKernel require base_path('routes/console.php'); } - private function registerScheduleCleanup(Schedule $schedule): void - { - if (!file_exists(base_path(".env")) || !Schema::hasTable("settings")) { - return; - } - $interval = get_setting("main.autoclean_interval_one"); - if (!$interval || $interval < 60) { - $interval = 7200; - } - $schedule->job(new CheckCleanup()) - ->cron(sprintf("*/%d * * * *", ceil($interval/60))) - ->withoutOverlapping(); - } } diff --git a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php index 7a7ecdfc..4c8871bb 100644 --- a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php +++ b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php @@ -15,6 +15,7 @@ use Filament\Facades\Filament; use Filament\Resources\Pages\Page; use Filament\Forms; use Illuminate\Support\HtmlString; +use Meilisearch\Contracts\Index\Settings; use Nexus\Database\NexusDB; class EditSetting extends Page implements Forms\Contracts\HasForms @@ -124,6 +125,7 @@ class EditSetting extends Page implements Forms\Contracts\HasForms // Forms\Components\TextInput::make('backup.google_drive_refresh_token')->label(__('label.setting.backup.google_drive_refresh_token')), // Forms\Components\TextInput::make('backup.google_drive_folder_id')->label(__('label.setting.backup.google_drive_folder_id')), Forms\Components\TextInput::make('backup.export_path')->label(__('label.setting.backup.export_path'))->helperText(new HtmlString(__('label.setting.backup.export_path_help', ['default_path' => ToolRepository::getBackupExportPathDefault()]))), + Forms\Components\TextInput::make('backup.retention_count')->numeric()->label(__('label.setting.backup.retention_count'))->helperText(new HtmlString(__('label.setting.backup.retention_count_help', ['default_count' => ToolRepository::BACKUP_RETENTION_COUNT_DEFAULT]))), Forms\Components\Radio::make('backup.via_ftp')->options(self::$yesOrNo)->inline(true)->label(__('label.setting.backup.via_ftp'))->helperText(new HtmlString(__('label.setting.backup.via_ftp_help'))), Forms\Components\Radio::make('backup.via_sftp')->options(self::$yesOrNo)->inline(true)->label(__('label.setting.backup.via_sftp'))->helperText(new HtmlString(__('label.setting.backup.via_sftp_help'))), ])->columns(2); diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 36debdba..8e5dcedf 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -253,5 +253,10 @@ class Setting extends NexusModel return self::get("backup.export_path"); } + public static function getBackupRetentionCount(): int + { + return (int)self::get("backup.retention_count"); + } + } diff --git a/app/Repositories/ToolRepository.php b/app/Repositories/ToolRepository.php index 6e251d56..ebf88085 100644 --- a/app/Repositories/ToolRepository.php +++ b/app/Repositories/ToolRepository.php @@ -12,6 +12,7 @@ use App\Models\User; use Carbon\Carbon; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Nexus\Database\NexusDB; @@ -27,6 +28,8 @@ class ToolRepository extends BaseRepository { const BACKUP_EXCLUDES = ['vendor', 'node_modules', '.git', '.idea', '.settings', '.DS_Store', '.github']; + const BACKUP_RETENTION_COUNT_DEFAULT = 10; + public function backupWeb($method = null, $transfer = false): array { $webRoot = base_path(); @@ -206,6 +209,7 @@ class ToolRepository extends BaseRepository $transferResult = $this->transfer($backupResult['filename'], $backupResult['result_code'], $setting); $backupResult['transfer_result'] = $transferResult; do_log("[BACKUP_ALL_DONE]: " . json_encode($backupResult)); + $this->cleanupBackupFiles(); return $backupResult; } @@ -325,6 +329,28 @@ class ToolRepository extends BaseRepository } } + private function cleanupBackupFiles(): void + { + $retentionCount = Setting::getBackupRetentionCount(); + if ($retentionCount <= 0) { + $retentionCount = self::BACKUP_RETENTION_COUNT_DEFAULT; + } + $path = self::getBackupExportPath(); + $allFiles = collect(File::allFiles($path)); + // 按创建时间降序排序 + $allFiles = $allFiles->sortByDesc(fn (\Symfony\Component\Finder\SplFileInfo $file) => $file->getCTime()); + $filesToDelete = $allFiles->slice($retentionCount); + do_log(sprintf( + "retentionCount: %s, path: %s, fileCount: %s, toDeleteCount: %s", + $retentionCount, $path, $allFiles->count(), $filesToDelete->count() + )); + foreach ($filesToDelete as $file) { + $realPath = $file->getRealPath(); + File::delete($realPath); + do_log(sprintf("delete backup file: %s", $realPath)); + } + } + /** * @param $to * @param $subject diff --git a/resources/lang/en/label.php b/resources/lang/en/label.php index 2ec774bd..fc4022f3 100644 --- a/resources/lang/en/label.php +++ b/resources/lang/en/label.php @@ -68,6 +68,8 @@ return [ 'via_sftp_help' => 'Whether to save via FTP. If so, add the configuration information to the .env file, refer to Laravel doc', 'export_path' => 'Export to directory', 'export_path_help' => 'Not set to use the system temporary directory::default_path. you can use third-party specialized tools to transfer offsite saves.' , + 'retention_count' => 'Retention count', + 'retention_count_help' => 'Retain only the latest backup records, old ones will be deleted regularly. Default: :default_count', ], 'hr' => [ 'tab_header' => 'H&R', diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php index 76d70355..3cfe0f27 100644 --- a/resources/lang/zh_CN/label.php +++ b/resources/lang/zh_CN/label.php @@ -68,6 +68,8 @@ return [ 'via_sftp_help' => '是否通过 SFTP 保存。如果通过,把配置信息添加到 .env 文件,参考 Laravel 文档', 'export_path' => '导出到目录', 'export_path_help' => '不设置使用系统临时目录::default_path。可以使用第三方专业工具转移异地保存。', + 'retention_count' => '保留数量', + 'retention_count_help' => '只保留最新的备份记录,旧的会定时删除。默认::default_count', ], 'hr' => [ 'tab_header' => 'H&R', diff --git a/resources/lang/zh_TW/label.php b/resources/lang/zh_TW/label.php index 5ac2a0f0..3f11edd1 100644 --- a/resources/lang/zh_TW/label.php +++ b/resources/lang/zh_TW/label.php @@ -68,6 +68,8 @@ return [ 'via_sftp_help' => '是否通過 SFTP 保存。如果通過,把配置信息添加到 .env 文件,參考 Laravel 文檔', 'export_path' => '導出到目錄', 'export_path_help' => '不設置使用系統臨時目錄::default_path。可以使用第三方專業工具轉移異地保存。', + 'retention_count' => '保留數量', + 'retention_count_help' => '只保留最新的備份記錄,舊的會定時刪除。默認::default_count', ], 'hr' => [ 'tab_header' => 'H&R',