Laravel 13 的任务调度系统提供了强大的定时任务管理能力,本文深入探讨高级用法和最佳实践。
任务调度基础回顾
定义调度任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('inspire') ->hourly(); $schedule->job(new ProcessPodcasts) ->daily() ->at('02:00'); $schedule->exec('node /path/to/script.js') ->daily(); } }
|
高级调度频率
复杂时间表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:generate') ->cron('0 2 * * 1,3,5'); $schedule->command('cache:warm') ->everyFifteenMinutes() ->between('08:00', '18:00') ->weekdays(); $schedule->command('analytics:aggregate') ->twiceDaily(1, 13) ->timezone('America/New_York'); $schedule->command('backup:incremental') ->everyOddHour() ->skip(function () { return now()->isHoliday(); }); $schedule->command('reports:weekly') ->weeklyOn(1, '08:00'); $schedule->command('invoices:generate') ->monthlyOn(1, '00:00') ->when(function () { return now()->month !== 12; }); $schedule->command('quarterly:report') ->quarterly() ->at('00:00'); $schedule->command('yearly:audit') ->yearlyOn(1, 1, '00:00'); } }
|
动态调度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <?php
namespace App\Console;
use App\Models\ScheduledTask; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $tasks = ScheduledTask::where('is_active', true)->get(); foreach ($tasks as $task) { $event = $schedule->command($task->command, $task->parameters ?? []); if ($task->expression) { $event->cron($task->expression); } else { match ($task->frequency) { 'every_minute' => $event->everyMinute(), 'hourly' => $event->hourly(), 'daily' => $event->daily(), 'weekly' => $event->weekly(), 'monthly' => $event->monthly(), default => $event->daily(), }; } if ($task->timezone) { $event->timezone($task->timezone); } if ($task->without_overlapping) { $event->withoutOverlapping($task->overlap_minutes ?? 1440); } if ($task->run_in_maintenance) { $event->evenInMaintenanceMode(); } $event->onSuccess(function () use ($task) { $task->update([ 'last_run_at' => now(), 'last_run_status' => 'success', ]); }); $event->onFailure(function () use ($task) { $task->update([ 'last_run_at' => now(), 'last_run_status' => 'failed', ]); }); } } }
|
任务约束条件
环境约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('telescope:prune') ->daily() ->environments(['production', 'staging']); $schedule->command('debug:clear') ->daily() ->environments('local'); $schedule->command('reports:generate') ->daily() ->when(function () { return app()->environment('production'); }); $schedule->command('sync:external') ->daily() ->skip(function () { return app()->isDownForMaintenance(); }); } }
|
条件约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <?php
namespace App\Console;
use App\Models\SystemSetting; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('notifications:send') ->daily() ->when(function () { return SystemSetting::get('notifications_enabled', true); }); $schedule->command('billing:process') ->daily() ->when(function () { return !now()->isWeekend() && !now()->isHoliday(); }); $schedule->command('reports:generate') ->daily() ->skip(function () { $maintenanceWindow = SystemSetting::get('maintenance_window'); if (!$maintenanceWindow) { return false; } $start = now()->parse($maintenanceWindow['start']); $end = now()->parse($maintenanceWindow['end']); return now()->between($start, $end); }); } }
|
任务输出管理
输出到文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:generate') ->daily() ->sendOutputTo(storage_path('logs/reports.log')) ->appendOutput(); $schedule->command('backup:full') ->daily() ->sendOutputTo(storage_path('logs/backup-' . date('Y-m-d') . '.log')); $schedule->command('import:data') ->daily() ->writeOutputTo(storage_path('logs/import.log'), true); } }
|
输出到邮件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:critical') ->daily() ->emailOutputTo('admin@example.com') ->emailOutputOnFailure('alerts@example.com'); $schedule->command('backup:full') ->daily() ->emailOutputTo(['admin@example.com', 'ops@example.com']) ->subject('Daily Backup Report'); } }
|
任务钩子
前置和后置钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
namespace App\Console;
use App\Services\TaskLogger; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:generate') ->daily() ->before(function () { app(TaskLogger::class)->logStart('reports:generate'); }) ->after(function () { app(TaskLogger::class)->logEnd('reports:generate'); }); $schedule->command('backup:full') ->daily() ->onSuccess(function () { app(TaskLogger::class)->logSuccess('backup:full'); }) ->onFailure(function () { app(TaskLogger::class)->logFailure('backup:full'); }); } }
|
任务生命周期管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <?php
namespace App\Services;
use App\Models\TaskExecution; use Illuminate\Support\Facades\Log;
class TaskLifecycleManager { protected TaskExecution $execution; public function start(string $taskName): void { $this->execution = TaskExecution::create([ 'task_name' => $taskName, 'started_at' => now(), 'status' => 'running', ]); Log::info("Task started: {$taskName}", [ 'execution_id' => $this->execution->id, ]); } public function success(): void { $this->execution->update([ 'finished_at' => now(), 'status' => 'success', ]); Log::info("Task completed successfully", [ 'execution_id' => $this->execution->id, 'duration' => $this->execution->started_at->diffInSeconds(now()), ]); } public function failure(\Throwable $exception): void { $this->execution->update([ 'finished_at' => now(), 'status' => 'failed', 'error_message' => $exception->getMessage(), ]); Log::error("Task failed", [ 'execution_id' => $this->execution->id, 'error' => $exception->getMessage(), ]); } }
|
防止任务重叠
基本防重叠
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:generate') ->daily() ->withoutOverlapping(); $schedule->command('import:large-dataset') ->daily() ->withoutOverlapping(120); $schedule->command('sync:external') ->everyMinute() ->withoutOverlapping(10) ->skip(function () { return cache()->has('sync:external:running'); }); } }
|
分布式锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Support\Facades\Cache;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:generate') ->daily() ->withoutOverlapping() ->onOneServer(); $schedule->command('billing:process') ->daily() ->withoutOverlapping() ->onOneServer(); } }
|
单服务器任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:generate') ->daily() ->onOneServer(); $schedule->command('analytics:aggregate') ->hourly() ->onOneServer(); $schedule->command('cleanup:temp') ->daily() ->onOneServer() ->runInBackground(); } }
|
后台任务执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('reports:generate') ->daily() ->runInBackground(); $schedule->command('analytics:process') ->hourly() ->runInBackground() ->withoutOverlapping(); } }
|
维护模式处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->command('backup:full') ->daily() ->evenInMaintenanceMode(); $schedule->command('reports:generate') ->daily() ->skip(function () { return app()->isDownForMaintenance(); }); } }
|
自定义调度命令
创建调度命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| <?php
namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan;
class ScheduleRunCommand extends Command { protected $signature = 'schedule:run-custom'; protected $description = 'Run scheduled tasks with custom logic'; public function handle(): int { $tasks = $this->getTasksToRun(); foreach ($tasks as $task) { $this->info("Running: {$task['name']}"); $startTime = microtime(true); try { Artisan::call($task['command'], $task['params'] ?? []); $output = Artisan::output(); $this->logSuccess($task, $output, microtime(true) - $startTime); } catch (\Throwable $e) { $this->logFailure($task, $e, microtime(true) - $startTime); } } return self::SUCCESS; } protected function getTasksToRun(): array { return [ [ 'name' => 'Daily Report', 'command' => 'reports:generate', 'params' => ['--type' => 'daily'], ], [ 'name' => 'Cache Warm', 'command' => 'cache:warm', ], ]; } protected function logSuccess(array $task, string $output, float $duration): void { $this->info("✓ {$task['name']} completed in {$duration}s"); } protected function logFailure(array $task, \Throwable $e, float $duration): void { $this->error("✗ {$task['name']} failed: {$e->getMessage()}"); } }
|
任务调度最佳实践
任务组织
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace App\Console;
use App\Console\Schedules\BackupSchedule; use App\Console\Schedules\ReportSchedule; use App\Console\Schedules\CleanupSchedule; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { app(BackupSchedule::class)($schedule); app(ReportSchedule::class)($schedule); app(CleanupSchedule::class)($schedule); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php
namespace App\Console\Schedules;
use Illuminate\Console\Scheduling\Schedule;
class BackupSchedule { public function __invoke(Schedule $schedule): void { $schedule->command('backup:full') ->daily() ->at('02:00') ->onOneServer() ->withoutOverlapping() ->emailOutputOnFailure('admin@example.com'); $schedule->command('backup:incremental') ->hourly() ->onOneServer(); $schedule->command('backup:cleanup') ->weekly() ->sundays() ->at('03:00'); } }
|
错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Console\Schedules;
use App\Services\ErrorNotifier; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Support\Facades\Log; use Throwable;
trait HandlesScheduleErrors { protected function withErrorHandling(Schedule $schedule, callable $callback): void { try { $callback($schedule); } catch (Throwable $e) { Log::error('Schedule configuration error', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); app(ErrorNotifier::class)->notify($e); } } }
|
总结
Laravel 13 的任务调度系统提供了丰富的功能来管理定时任务。通过合理使用约束条件、钩子函数和防重叠机制,可以构建可靠的后台任务处理系统。关键是要组织好任务代码,并做好错误处理和监控。