Laravel 13 任务调度器提供了强大的定时任务管理能力,本文深入探讨调度器的高级用法。
任务调度器架构
调度器配置
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
| <?php
namespace App\Console;
use App\Console\Schedules\DailyTasks; use App\Console\Schedules\HourlyTasks; use App\Console\Schedules\WeeklyTasks; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { $schedule->call(new DailyTasks)->daily(); $schedule->call(new HourlyTasks)->hourly(); $schedule->call(new WeeklyTasks)->weekly(); $schedule->command('cache:warm') ->hourly() ->withoutOverlapping() ->onOneServer(); $schedule->command('reports:generate') ->dailyAt('02:00') ->emailOutputTo('admin@example.com'); } }
|
调度任务类
可调用任务
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
| <?php
namespace App\Console\Schedules;
use App\Services\Report\ReportGenerator; use Illuminate\Contracts\Scheduling\ShouldSchedule;
class DailyTasks implements ShouldSchedule { public function __construct( protected ReportGenerator $reports ) {} public function __invoke(): void { $this->cleanupExpiredTokens(); $this->generateDailyReports(); $this->sendDailyNotifications(); } protected function cleanupExpiredTokens(): void { \App\Models\Token::where('expires_at', '<', now())->delete(); } protected function generateDailyReports(): void { $this->reports->generateDaily(); } protected function sendDailyNotifications(): void { \App\Jobs\SendDailyNotifications::dispatch(); } }
|
条件任务
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
| <?php
namespace App\Console\Schedules;
use App\Models\SystemSetting; use Illuminate\Contracts\Scheduling\ShouldSchedule;
class ConditionalTasks implements ShouldSchedule { public function __invoke(): void { if ($this->shouldRunBackups()) { $this->runBackup(); } if ($this->shouldSendReports()) { $this->sendReports(); } } protected function shouldRunBackups(): bool { return SystemSetting::get('backup_enabled', true); } protected function shouldSendReports(): bool { return !now()->isWeekend() && !now()->isHoliday(); } protected function runBackup(): void { \Illuminate\Support\Facades\Artisan::call('backup:run'); } protected function sendReports(): void { \App\Jobs\SendReports::dispatch(); } }
|
调度器管理
调度器服务
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| <?php
namespace App\Services\Scheduler;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Support\Collection;
class SchedulerService { protected Schedule $schedule; protected Collection $tasks; public function __construct(Schedule $schedule) { $this->schedule = $schedule; $this->tasks = collect(); } public function register(ScheduledTask $task): self { $this->tasks->push($task); $event = $this->schedule->command($task->command, $task->parameters ?? []); $this->applyFrequency($event, $task); $this->applyConstraints($event, $task); $this->applyHooks($event, $task); return $this; } protected function applyFrequency($event, ScheduledTask $task): void { match ($task->frequency) { 'every_minute' => $event->everyMinute(), 'every_five_minutes' => $event->everyFiveMinutes(), 'every_fifteen_minutes' => $event->everyFifteenMinutes(), 'hourly' => $event->hourly(), 'daily' => $event->daily(), 'weekly' => $event->weekly(), 'monthly' => $event->monthly(), default => $event->cron($task->expression), }; } protected function applyConstraints($event, ScheduledTask $task): void { if ($task->timezone) { $event->timezone($task->timezone); } if ($task->without_overlapping) { $event->withoutOverlapping($task->overlap_minutes ?? 1440); } if ($task->on_one_server) { $event->onOneServer(); } if ($task->run_in_maintenance) { $event->evenInMaintenanceMode(); } if ($task->environments) { $event->environments($task->environments); } } protected function applyHooks($event, ScheduledTask $task): void { if ($task->before_callback) { $event->before($task->before_callback); } if ($task->after_callback) { $event->after($task->after_callback); } if ($task->on_success_callback) { $event->onSuccess($task->on_success_callback); } if ($task->on_failure_callback) { $event->onFailure($task->on_failure_callback); } } public function getTasks(): Collection { return $this->tasks; } }
|
动态调度
数据库驱动的调度
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 60 61 62 63 64 65 66 67 68 69 70
| <?php
namespace App\Services\Scheduler;
use App\Models\ScheduledTask; use Illuminate\Console\Scheduling\Schedule;
class DynamicScheduler { protected Schedule $schedule; public function __construct(Schedule $schedule) { $this->schedule = $schedule; } public function loadFromDatabase(): void { $tasks = ScheduledTask::where('is_active', true)->get(); foreach ($tasks as $task) { $this->registerTask($task); } } protected function registerTask(ScheduledTask $task): void { $event = $this->schedule->command($task->command, $task->parameters ?? []); if ($task->expression) { $event->cron($task->expression); } else { $this->applyFrequency($event, $task); } if ($task->without_overlapping) { $event->withoutOverlapping($task->overlap_minutes ?? 1440); } if ($task->on_one_server) { $event->onOneServer(); } $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', ]); }); } protected function applyFrequency($event, ScheduledTask $task): void { match ($task->frequency) { 'every_minute' => $event->everyMinute(), 'hourly' => $event->hourly(), 'daily' => $event->daily(), 'weekly' => $event->weekly(), 'monthly' => $event->monthly(), default => $event->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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <?php
namespace App\Services\Scheduler;
use App\Models\TaskExecution; use Illuminate\Console\Events\ScheduledTaskFinished; use Illuminate\Console\Events\ScheduledTaskStarting; use Illuminate\Events\Dispatcher;
class SchedulerMonitor { protected array $executions = []; public function subscribe(Dispatcher $events): void { $events->listen(ScheduledTaskStarting::class, [$this, 'taskStarting']); $events->listen(ScheduledTaskFinished::class, [$this, 'taskFinished']); } public function taskStarting(ScheduledTaskStarting $event): void { $execution = TaskExecution::create([ 'task_name' => $this->getTaskName($event), 'command' => $event->task->command ?? $event->task->description, 'started_at' => now(), 'status' => 'running', ]); $this->executions[$this->getTaskKey($event)] = $execution; } public function taskFinished(ScheduledTaskFinished $event): void { $key = $this->getTaskKey($event); if (!isset($this->executions[$key])) { return; } $execution = $this->executions[$key]; $execution->update([ 'finished_at' => now(), 'duration' => $execution->started_at->diffInSeconds(now()), 'status' => $event->task->exitCode === 0 ? 'success' : 'failed', 'exit_code' => $event->task->exitCode, 'output' => $event->task->output ?? null, ]); unset($this->executions[$key]); } protected function getTaskName($event): string { return $event->task->description ?? $event->task->command ?? 'unknown'; } protected function getTaskKey($event): string { return spl_object_hash($event->task); } }
|
调度器命令
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| <?php
namespace App\Console\Commands;
use App\Models\ScheduledTask; use Illuminate\Console\Command;
class SchedulerCommand extends Command { protected $signature = 'scheduler:manage {action} {task?}'; protected $description = 'Manage scheduled tasks'; public function handle(): int { $action = $this->argument('action'); return match ($action) { 'list' => $this->listTasks(), 'enable' => $this->enableTask(), 'disable' => $this->disableTask(), 'run' => $this->runTask(), default => $this->invalidAction(), }; } protected function listTasks(): int { $tasks = ScheduledTask::all(); $this->table( ['ID', 'Command', 'Frequency', 'Active', 'Last Run'], $tasks->map(fn($t) => [ $t->id, $t->command, $t->frequency ?? $t->expression, $t->is_active ? 'Yes' : 'No', $t->last_run_at?->diffForHumans() ?? 'Never', ]) ); return self::SUCCESS; } protected function enableTask(): int { $taskId = $this->argument('task'); if (!$taskId) { $this->error('Please specify a task ID'); return self::FAILURE; } ScheduledTask::where('id', $taskId)->update(['is_active' => true]); $this->info("Task {$taskId} enabled"); return self::SUCCESS; } protected function disableTask(): int { $taskId = $this->argument('task'); if (!$taskId) { $this->error('Please specify a task ID'); return self::FAILURE; } ScheduledTask::where('id', $taskId)->update(['is_active' => false]); $this->info("Task {$taskId} disabled"); return self::SUCCESS; } protected function runTask(): int { $taskId = $this->argument('task'); if (!$taskId) { $this->error('Please specify a task ID'); return self::FAILURE; } $task = ScheduledTask::find($taskId); if (!$task) { $this->error('Task not found'); return self::FAILURE; } $this->info("Running task: {$task->command}"); \Illuminate\Support\Facades\Artisan::call($task->command, $task->parameters ?? []); $this->line(\Illuminate\Support\Facades\Artisan::output()); return self::SUCCESS; } protected function invalidAction(): int { $this->error('Invalid action. Use: list, enable, disable, or run'); return self::FAILURE; } }
|
总结
Laravel 13 任务调度器提供了灵活的定时任务管理能力。通过可调用任务、动态调度和监控机制,可以构建可靠的定时任务系统。