Laravel 任务调度与计划

在开发 Web 应用时,经常需要定时执行某些任务,比如每天清理临时文件、每小时同步数据、每分钟发送报告。传统做法是在服务器上添加 Cron 条目,但管理大量 Cron 命令会变得混乱。Laravel 提供了优雅的任务调度(Task Scheduling)功能,让你在代码中定义调度规则,只需在服务器上设置一个 Cron 入口,即可轻松管理所有定时任务。

⏰ 核心优势: 所有调度逻辑都在版本控制中(routes/console.php),无需 SSH 到服务器维护 Cron,调度器支持丰富的频率设置、并行限制、输出处理等。

1. 快速开始:定义第一个调度任务

所有调度任务都在 routes/console.php 文件中定义。使用 Schedule facade 的 commandexeccall 等方法。


use Illuminate\Support\Facades\Schedule;

// 每天午夜执行一个 Artisan 命令
Schedule::command('emails:send')->daily();

// 每小时调用一次闭包
Schedule::call(function () {
    DB::table('recent_users')->delete();
})->hourly();

// 每分钟执行一个 shell 命令
Schedule::exec('node /path/to/script.js')->everyMinute();
                    

定义好调度后,需要在服务器上添加唯一的 Cron 条目,每分钟触发 Laravel 调度器:


* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
                    

这样 Laravel 就会自动根据你的定义执行任务。

2. 调度 Artisan 命令

最常用的是调度自定义 Artisan 命令。使用 command() 方法传入命令签名。


Schedule::command('emails:send --force')->dailyAt('02:00');

// 也可以传入完整类名
Schedule::command(\App\Console\Commands\SendEmails::class)->mondays();
                    

3. 调度闭包与 Shell 命令

简单的逻辑可以直接用闭包,避免创建独立命令。但注意闭包内不要使用 dd()exit,否则会中断调度器。


Schedule::call(function () {
    $userCount = User::whereDate('created_at', today())->count();
    Log::info("今日新增用户: {$userCount}");
})->dailyAt('23:50');
                    

执行系统命令使用 exec(),可以捕获输出:


Schedule::exec('php artisan backup:run')
        ->daily()
        ->sendOutputTo(storage_path('logs/backup.log'));
                    

4. 丰富的频率设置

Laravel 提供了大量方法设置任务执行频率,以下列出常用选项:

方法说明
->cron('* * * * *')自定义 Cron 表达式
->everyMinute()每分钟执行
->everyTwoMinutes()每两分钟
->hourly()每小时整点
->hourlyAt(17)每小时的第 17 分钟
->daily()每天午夜
->dailyAt('13:00')每天 13:00
->twiceDaily(1, 13)每天 1:00 和 13:00
->weekly()每周日 0:00
->monthly()每月1号 0:00
->weekdays()仅工作日
->weekends()仅周末
->sundays() / ->mondays()指定星期几

还可以组合约束条件,例如:


Schedule::command('reports:generate')
    ->dailyAt('23:00')
    ->between('22:00', '06:00')
    ->when(function () {
        return date('D') !== 'Sat';
    });
                    

5. 避免任务重叠

有些任务执行时间可能超过间隔时间(例如一个每分钟执行的任务运行了 2 分钟)。使用 withoutOverlapping() 可以确保同一任务不会重叠,Laravel 会使用应用缓存作为锁。


Schedule::command('emails:send')->everyMinute()->withoutOverlapping();
                    

默认锁有效期 24 小时,可以自定义过期时间:


->withoutOverlapping(10)  // 10 分钟后释放锁
                    

6. 维护模式下的调度

当 Laravel 处于维护模式(php artisan down)时,默认所有调度任务都不会执行。如果你希望某些任务在维护模式下仍运行,可以调用 evenInMaintenanceMode()


Schedule::command('backup:run')->daily()->evenInMaintenanceMode();
                    

7. 输出重定向与任务钩子

你可以将任务的输出重定向到文件,或通过邮件发送。


// 追加到文件
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo(storage_path('logs/email.log'));

// 发送邮件给指定地址
Schedule::command('reports:generate')
    ->daily()
    ->emailOutputTo('admin@example.com');

// 仅当输出非空时才发送邮件
->emailOutputOnFailure('admin@example.com');
                    

任务钩子允许在任务执行前后执行回调:


Schedule::command('backup:run')
    ->daily()
    ->before(function () {
        Log::info('备份开始');
    })
    ->after(function () {
        Log::info('备份完成');
    });
                    

8. 测试调度任务

你可以使用 schedule:list 命令查看所有已定义的调度任务:


php artisan schedule:list
                    

手动触发某个调度任务(便于测试):


php artisan schedule:run
                    

使用 --verbose 查看详细输出。

9. 全局钩子与回调

你可以在 App\Console\Kernel 类的 schedule() 方法外使用 Schedule::before()Schedule::after() 来定义所有任务的前置/后置操作。


// 在 App\Console\Kernel 的 schedule 方法中
protected function schedule(Schedule $schedule)
{
    $schedule->command('emails:send')->daily();

    $schedule->before(function () {
        // 所有任务执行前
    });

    $schedule->after(function () {
        // 所有任务执行后
    });
}
                    

10. 最佳实践与注意事项

  • 确保唯一 Cron 条目: 服务器上只需一条 * * * * * php artisan schedule:run,所有调度逻辑在代码中管理。
  • 避免任务重叠: 对长时间运行的任务务必使用 withoutOverlapping()
  • 使用环境变量: 调度任务中避免硬编码路径,使用 base_path()storage_path() 等辅助函数。
  • 监控失败: 利用 emailOutputOnFailure() 或集成监控系统(如 Oh Dear, Healthchecks)感知任务失败。
  • 测试调度: 在部署前使用 schedule:list 验证调度定义是否符合预期。
  • 维护模式: 默认调度不执行,需要运行的关键任务显式调用 evenInMaintenanceMode()
💡 提示: Laravel 11 中调度器性能得到提升,且支持更灵活的并发控制。对于高负载场景,建议结合队列系统处理耗时任务。

完整示例:备份与清理调度


// 在 routes/console.php 中
use Illuminate\Support\Facades\Schedule;

Schedule::command('backup:run')
    ->dailyAt('02:00')
    ->withoutOverlapping()
    ->emailOutputOnFailure('admin@example.com');

Schedule::call(function () {
    $files = glob(storage_path('temp/*'));
    foreach ($files as $file) {
        if (is_file($file) && filemtime($file) < now()->subDays(7)->timestamp) {
            unlink($file);
        }
    }
})->weekly()->sundays()->at('03:00');
                    

11. 总结

Laravel 任务调度器将复杂的 Cron 管理简化为优雅、可读的 PHP 代码,极大地提高了定时任务的可维护性。通过掌握调度定义、频率选项、重叠控制、输出处理等特性,你可以轻松实现各种周期性任务,并确保它们在正确的时间以正确的方式运行。

📖 官方文档: Laravel Scheduling 提供了更完整的频率方法和高级配置。