任务调度 
介绍 
过去,您可能为每个需要在服务器上调度的任务编写了一个 cron 配置条目。然而,这很快就会变得麻烦,因为您的任务调度不再在源代码控制中,您必须 SSH 进入服务器以查看现有的 cron 条目或添加其他条目。
Laravel 的命令调度器提供了一种全新的方法来管理服务器上的计划任务。调度器允许您在 Laravel 应用程序中流畅且富有表现力地定义命令调度。使用调度器时,服务器上只需要一个 cron 条目。您的任务调度在 app/Console/Kernel.php 文件的 schedule 方法中定义。为了帮助您入门,方法中定义了一个简单的示例。
定义调度 
您可以在应用程序的 App\Console\Kernel 类的 schedule 方法中定义所有计划任务。首先,让我们看一个示例。在此示例中,我们将调度一个闭包,每天午夜调用。在闭包中,我们将执行一个数据库查询以清空一个表:
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
    /**
     * 定义应用程序的命令调度。
     */
    protected function schedule(Schedule $schedule): void
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}除了使用闭包进行调度,您还可以调度 可调用对象。可调用对象是包含 __invoke 方法的简单 PHP 类:
$schedule->call(new DeleteRecentUsers)->daily();如果您想查看计划任务的概览以及它们下次计划运行的时间,可以使用 schedule:list Artisan 命令:
php artisan schedule:list调度 Artisan 命令 
除了调度闭包,您还可以调度 Artisan 命令 和系统命令。例如,您可以使用 command 方法通过命令的名称或类调度 Artisan 命令。
在使用命令的类名调度 Artisan 命令时,您可以传递一个额外的命令行参数数组,这些参数将在命令调用时提供给命令:
use App\Console\Commands\SendEmailsCommand;
$schedule->command('emails:send Taylor --force')->daily();
$schedule->command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();调度队列作业 
job 方法可用于调度 队列作业。此方法提供了一种方便的方法来调度队列作业,而无需使用 call 方法定义闭包来排队作业:
use App\Jobs\Heartbeat;
$schedule->job(new Heartbeat)->everyFiveMinutes();可以为 job 方法提供可选的第二个和第三个参数,指定用于排队作业的队列名称和队列连接:
use App\Jobs\Heartbeat;
// 将作业调度到 "heartbeats" 队列上的 "sqs" 连接...
$schedule->job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();调度 Shell 命令 
exec 方法可用于向操作系统发出命令:
$schedule->exec('node /home/forge/script.js')->daily();调度频率选项 
我们已经看到了一些如何配置任务在指定间隔运行的示例。然而,您可以为任务分配更多的任务调度频率:
| 方法 | 描述 | 
|---|---|
| ->cron('* * * * *'); | 在自定义 cron 调度上运行任务 | 
| ->everySecond(); | 每秒运行任务 | 
| ->everyTwoSeconds(); | 每两秒运行任务 | 
| ->everyFiveSeconds(); | 每五秒运行任务 | 
| ->everyTenSeconds(); | 每十秒运行任务 | 
| ->everyFifteenSeconds(); | 每十五秒运行任务 | 
| ->everyTwentySeconds(); | 每二十秒运行任务 | 
| ->everyThirtySeconds(); | 每三十秒运行任务 | 
| ->everyMinute(); | 每分钟运行任务 | 
| ->everyTwoMinutes(); | 每两分钟运行任务 | 
| ->everyThreeMinutes(); | 每三分钟运行任务 | 
| ->everyFourMinutes(); | 每四分钟运行任务 | 
| ->everyFiveMinutes(); | 每五分钟运行任务 | 
| ->everyTenMinutes(); | 每十分钟运行任务 | 
| ->everyFifteenMinutes(); | 每十五分钟运行任务 | 
| ->everyThirtyMinutes(); | 每三十分钟运行任务 | 
| ->hourly(); | 每小时运行任务 | 
| ->hourlyAt(17); | 每小时在 17 分钟时运行任务 | 
| ->everyOddHour($minutes = 0); | 每个奇数小时运行任务 | 
| ->everyTwoHours($minutes = 0); | 每两小时运行任务 | 
| ->everyThreeHours($minutes = 0); | 每三小时运行任务 | 
| ->everyFourHours($minutes = 0); | 每四小时运行任务 | 
| ->everySixHours($minutes = 0); | 每六小时运行任务 | 
| ->daily(); | 每天午夜运行任务 | 
| ->dailyAt('13:00'); | 每天 13:00 运行任务 | 
| ->twiceDaily(1, 13); | 每天 1:00 和 13:00 运行任务 | 
| ->twiceDailyAt(1, 13, 15); | 每天 1:15 和 13:15 运行任务 | 
| ->weekly(); | 每周日 00:00 运行任务 | 
| ->weeklyOn(1, '8:00'); | 每周一 8:00 运行任务 | 
| ->monthly(); | 每月的第一天 00:00 运行任务 | 
| ->monthlyOn(4, '15:00'); | 每月 4 日 15:00 运行任务 | 
| ->twiceMonthly(1, 16, '13:00'); | 每月 1 日和 16 日 13:00 运行任务 | 
| ->lastDayOfMonth('15:00'); | 每月的最后一天 15:00 运行任务 | 
| ->quarterly(); | 每季度的第一天 00:00 运行任务 | 
| ->quarterlyOn(4, '14:00'); | 每季度 4 日 14:00 运行任务 | 
| ->yearly(); | 每年的第一天 00:00 运行任务 | 
| ->yearlyOn(6, 1, '17:00'); | 每年 6 月 1 日 17:00 运行任务 | 
| ->timezone('America/New_York'); | 设置任务的时区 | 
这些方法可以与其他约束结合使用,以创建更精细的调度,仅在一周的某些天运行。例如,您可以调度命令在每周一运行:
// 每周一下午 1 点运行一次...
$schedule->call(function () {
    // ...
})->weekly()->mondays()->at('13:00');
// 在工作日的上午 8 点到下午 5 点之间每小时运行一次...
$schedule->command('foo')
          ->weekdays()
          ->hourly()
          ->timezone('America/Chicago')
          ->between('8:00', '17:00');以下是其他调度约束的列表:
| 方法 | 描述 | 
|---|---|
| ->weekdays(); | 限制任务在工作日运行 | 
| ->weekends(); | 限制任务在周末运行 | 
| ->sundays(); | 限制任务在周日运行 | 
| ->mondays(); | 限制任务在周一运行 | 
| ->tuesdays(); | 限制任务在周二运行 | 
| ->wednesdays(); | 限制任务在周三运行 | 
| ->thursdays(); | 限制任务在周四运行 | 
| ->fridays(); | 限制任务在周五运行 | 
| ->saturdays(); | 限制任务在周六运行 | 
| ->days(array|mixed); | 限制任务在特定天运行 | 
| ->between($startTime, $endTime); | 限制任务在开始和结束时间之间运行 | 
| ->unlessBetween($startTime, $endTime); | 限制任务不在开始和结束时间之间运行 | 
| ->when(Closure); | 基于真值测试限制任务 | 
| ->environments($env); | 限制任务在特定环境中运行 | 
天约束 
days 方法可用于限制任务在一周的特定天运行。例如,您可以调度命令在周日和周三每小时运行:
$schedule->command('emails:send')
                ->hourly()
                ->days([0, 3]);或者,您可以在定义任务应运行的天时使用 Illuminate\Console\Scheduling\Schedule 类中可用的常量:
use Illuminate\Console\Scheduling\Schedule;
$schedule->command('emails:send')
                ->hourly()
                ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);时间约束 
between 方法可用于根据一天中的时间限制任务的执行:
$schedule->command('emails:send')
                    ->hourly()
                    ->between('7:00', '22:00');同样,unlessBetween 方法可用于排除任务在一段时间内的执行:
$schedule->command('emails:send')
                    ->hourly()
                    ->unlessBetween('23:00', '4:00');真值测试约束 
when 方法可用于根据给定真值测试的结果限制任务的执行。换句话说,如果给定的闭包返回 true,则任务将执行,只要没有其他限制条件阻止任务运行:
$schedule->command('emails:send')->daily()->when(function () {
    return true;
});skip 方法可以看作是 when 的反义词。如果 skip 方法返回 true,则计划任务将不会执行:
$schedule->command('emails:send')->daily()->skip(function () {
    return true;
});使用链式 when 方法时,计划命令仅在所有 when 条件返回 true 时执行。
环境约束 
environments 方法可用于仅在给定环境中执行任务(由 APP_ENV 环境变量 定义):
$schedule->command('emails:send')
            ->daily()
            ->environments(['staging', 'production']);时区 
使用 timezone 方法,您可以指定计划任务的时间应在给定时区内解释:
$schedule->command('report:generate')
         ->timezone('America/New_York')
         ->at('2:00')如果您反复为所有计划任务分配相同的时区,您可能希望在 App\Console\Kernel 类中定义一个 scheduleTimezone 方法。此方法应返回应分配给所有计划任务的默认时区:
use DateTimeZone;
/**
 * 获取应默认用于计划事件的时区。
 */
protected function scheduleTimezone(): DateTimeZone|string|null
{
    return 'America/Chicago';
}WARNING
请记住,某些时区使用夏令时。当夏令时变化发生时,您的计划任务可能会运行两次甚至不运行。因此,我们建议尽可能避免使用时区调度。
防止任务重叠 
默认情况下,即使任务的上一个实例仍在运行,计划任务也会运行。为防止这种情况,您可以使用 withoutOverlapping 方法:
$schedule->command('emails:send')->withoutOverlapping();在此示例中,如果 emails:send Artisan 命令 尚未运行,则每分钟运行一次。withoutOverlapping 方法在任务执行时间差异很大的情况下特别有用,防止您无法准确预测给定任务需要多长时间。
如果需要,您可以指定在“无重叠”锁过期之前必须经过多少分钟。默认情况下,锁将在 24 小时后过期:
$schedule->command('emails:send')->withoutOverlapping(10);在后台,withoutOverlapping 方法利用应用程序的 缓存 获取锁。如果需要,您可以使用 schedule:clear-cache Artisan 命令清除这些缓存锁。这通常仅在由于意外的服务器问题导致任务卡住时才需要。
在一台服务器上运行任务 
WARNING
要使用此功能,您的应用程序必须使用 database、memcached、dynamodb 或 redis 缓存驱动程序作为应用程序的默认缓存驱动程序。此外,所有服务器必须与同一个中央缓存服务器通信。
如果应用程序的调度器在多台服务器上运行,您可以限制计划作业仅在一台服务器上执行。例如,假设您有一个计划任务,每周五晚上生成一个新报告。如果任务调度器在三台工作服务器上运行,计划任务将在所有三台服务器上运行并生成报告三次。这不好!
要指示任务仅在一台服务器上运行,请在定义计划任务时使用 onOneServer 方法。第一个获取任务的服务器将对作业获得原子锁,以防止其他服务器同时运行相同的任务:
$schedule->command('report:generate')
                ->fridays()
                ->at('17:00')
                ->onOneServer();命名单服务器作业 
有时您可能需要调度相同的作业以不同的参数调度,同时指示 Laravel 在一台服务器上运行作业的每个排列。为此,您可以通过 name 方法为每个调度定义分配一个唯一名称:
$schedule->job(new CheckUptime('https://laravel.com'))
            ->name('check_uptime:laravel.com')
            ->everyFiveMinutes()
            ->onOneServer();
$schedule->job(new CheckUptime('https://vapor.laravel.com'))
            ->name('check_uptime:vapor.laravel.com')
            ->everyFiveMinutes()
            ->onOneServer();同样,如果计划的闭包打算在一台服务器上运行,则必须为其分配一个名称:
$schedule->call(fn () => User::resetApiRequestCount())
    ->name('reset-api-request-count')
    ->daily()
    ->onOneServer();后台任务 
默认情况下,同时计划的多个任务将根据它们在 schedule 方法中的定义顺序顺序执行。如果您有长时间运行的任务,这可能会导致后续任务的启动时间比预期的要晚得多。如果您希望在后台运行任务,以便它们可以同时运行,可以使用 runInBackground 方法:
$schedule->command('analytics:report')
         ->daily()
         ->runInBackground();WARNING
runInBackground 方法仅可用于通过 command 和 exec 方法调度任务。
维护模式 
当应用程序处于 维护模式 时,应用程序的计划任务将不会运行,因为我们不希望您的任务干扰您可能正在服务器上执行的任何未完成的维护。然而,如果您希望即使在维护模式下也强制任务运行,可以在定义任务时调用 evenInMaintenanceMode 方法:
$schedule->command('emails:send')->evenInMaintenanceMode();运行调度器 
现在我们已经了解了如何定义计划任务,让我们讨论如何在服务器上实际运行它们。schedule:run Artisan 命令将评估所有计划任务,并根据服务器的当前时间确定它们是否需要运行。
因此,使用 Laravel 的调度器时,我们只需要在服务器上添加一个 cron 配置条目,每分钟运行一次 schedule:run 命令。如果您不知道如何将 cron 条目添加到服务器,请考虑使用 Laravel Forge 等服务来管理 cron 条目:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1亚分钟调度任务 
在大多数操作系统上,cron 作业最多只能每分钟运行一次。然而,Laravel 的调度器允许您调度任务以更频繁的间隔运行,甚至可以每秒运行一次:
$schedule->call(function () {
    DB::table('recent_users')->delete();
})->everySecond();当应用程序中定义了亚分钟任务时,schedule:run 命令将在当前分钟结束之前继续运行,而不是立即退出。这允许命令在整个分钟内调用所有需要的亚分钟任务。
由于运行时间超过预期的亚分钟任务可能会延迟后续亚分钟任务的执行,建议所有亚分钟任务调度队列作业或后台命令以处理实际任务处理:
use App\Jobs\DeleteRecentUsers;
$schedule->job(new DeleteRecentUsers)->everyTenSeconds();
$schedule->command('users:delete')->everyTenSeconds()->runInBackground();中断亚分钟任务 
由于定义了亚分钟任务时,schedule:run 命令在调用的整个分钟内运行,因此在部署应用程序时,您可能需要中断命令。否则,已经运行的 schedule:run 命令实例将继续使用应用程序先前部署的代码,直到当前分钟结束。
要中断正在进行的 schedule:run 调用,您可以将 schedule:interrupt 命令添加到应用程序的部署脚本中。此命令应在应用程序完成部署后调用:
php artisan schedule:interrupt本地运行调度器 
通常,您不会在本地开发机器上添加调度器 cron 条目。相反,您可以使用 schedule:work Artisan 命令。此命令将在前台运行,并每分钟调用调度器,直到您终止命令:
php artisan schedule:work任务输出 
Laravel 调度器提供了几种方便的方法来处理计划任务生成的输出。首先,使用 sendOutputTo 方法,您可以将输出发送到文件以供以后检查:
$schedule->command('emails:send')
         ->daily()
         ->sendOutputTo($filePath);如果您希望将输出附加到给定文件,可以使用 appendOutputTo 方法:
$schedule->command('emails:send')
         ->daily()
         ->appendOutputTo($filePath);使用 emailOutputTo 方法,您可以将输出发送到您选择的电子邮件地址。在发送任务输出之前,您应该配置 Laravel 的 电子邮件服务:
$schedule->command('report:generate')
         ->daily()
         ->sendOutputTo($filePath)
         ->emailOutputTo('taylor@example.com');如果您只希望在计划的 Artisan 或系统命令以非零退出代码终止时发送输出,请使用 emailOutputOnFailure 方法:
$schedule->command('report:generate')
         ->daily()
         ->emailOutputOnFailure('taylor@example.com');WARNING
emailOutputTo、emailOutputOnFailure、sendOutputTo 和 appendOutputTo 方法仅适用于 command 和 exec 方法。
任务钩子 
使用 before 和 after 方法,您可以指定在计划任务执行之前和之后执行的代码:
$schedule->command('emails:send')
         ->daily()
         ->before(function () {
             // 任务即将执行...
         })
         ->after(function () {
             // 任务已执行...
         });onSuccess 和 onFailure 方法允许您指定在计划任务成功或失败时执行的代码。失败表示计划的 Artisan 或系统命令以非零退出代码终止:
$schedule->command('emails:send')
         ->daily()
         ->onSuccess(function () {
             // 任务成功...
         })
         ->onFailure(function () {
             // 任务失败...
         });如果命令有可用的输出,您可以通过在钩子闭包定义的 $output 参数中类型提示 Illuminate\Support\Stringable 实例来访问 after、onSuccess 或 onFailure 钩子中的输出:
use Illuminate\Support\Stringable;
$schedule->command('emails:send')
         ->daily()
         ->onSuccess(function (Stringable $output) {
             // 任务成功...
         })
         ->onFailure(function (Stringable $output) {
             // 任务失败...
         });Ping URL 
使用 pingBefore 和 thenPing 方法,调度器可以在任务执行之前或之后自动 ping 给定的 URL。此方法对于通知外部服务(如 Envoyer)您的计划任务开始或已完成执行非常有用:
$schedule->command('emails:send')
         ->daily()
         ->pingBefore($url)
         ->thenPing($url);pingBeforeIf 和 thenPingIf 方法可用于仅在给定条件为 true 时 ping 给定的 URL:
$schedule->command('emails:send')
         ->daily()
         ->pingBeforeIf($condition, $url)
         ->thenPingIf($condition, $url);pingOnSuccess 和 pingOnFailure 方法可用于仅在任务成功或失败时 ping 给定的 URL。失败表示计划的 Artisan 或系统命令以非零退出代码终止:
$schedule->command('emails:send')
         ->daily()
         ->pingOnSuccess($successUrl)
         ->pingOnFailure($failureUrl);所有 ping 方法都需要 Guzzle HTTP 库。Guzzle 通常默认安装在所有新的 Laravel 项目中,但如果它被意外删除,您可以使用 Composer 包管理器手动将 Guzzle 安装到项目中:
composer require guzzlehttp/guzzle事件 
如果需要,您可以监听调度器调度的 事件。通常,事件监听器映射将在应用程序的 App\Providers\EventServiceProvider 类中定义:
/**
 * 应用程序的事件监听器映射。
 *
 * @var array
 */
protected $listen = [
    'Illuminate\Console\Events\ScheduledTaskStarting' => [
        'App\Listeners\LogScheduledTaskStarting',
    ],
    'Illuminate\Console\Events\ScheduledTaskFinished' => [
        'App\Listeners\LogScheduledTaskFinished',
    ],
    'Illuminate\Console\Events\ScheduledBackgroundTaskFinished' => [
        'App\Listeners\LogScheduledBackgroundTaskFinished',
    ],
    'Illuminate\Console\Events\ScheduledTaskSkipped' => [
        'App\Listeners\LogScheduledTaskSkipped',
    ],
    'Illuminate\Console\Events\ScheduledTaskFailed' => [
        'App\Listeners\LogScheduledTaskFailed',
    ],
];