> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 任务调度

> 了解如何使用 Laravel 调度器优雅地管理定时任务。

## 什么是任务调度

在传统方式下，你需要为每个定时任务手写 cron 条目。
这样做的问题是调度定义脱离源码，无法进行版本管理，每次调整都要 SSH 登录服务器。

Laravel 的调度器允许你**在应用内部以流畅的语法定义调度**。
只需要在服务器上加一行 cron 条目，其他调度定义都随代码一起进入版本管理。

标准写法是把调度定义写在 `routes/console.php` 里。

```php theme={null}
<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

<Info>
  使用 `schedule:list` Artisan 命令可以查看已定义任务的列表以及下一次执行时间。

  ```shell theme={null}
  php artisan schedule:list
  ```
</Info>

### 调度器的执行流程

```mermaid theme={null}
flowchart TD
    A["* * * * * artisan schedule:run<br>（cron 每分钟触发）"] --> B["Console Kernel"]
    B --> C["调用 schedule() 方法"]
    C --> D["依次评估已注册的任务"]
    D --> E{"Cron 表达式是否<br>匹配当前时刻?"}
    E -->|"不匹配"| F["跳过"]
    E -->|"匹配"| G{"检查 when / skip /<br>environments 条件"}
    G -->|"不满足"| F
    G -->|"满足"| H{"当前是否<br>维护模式?"}
    H -->|"正常模式"| I["执行任务<br>(before → 任务体 → after)"]
    H -->|"维护模式"| J{"是否设置了<br>evenInMaintenanceMode()?"}
    J -->|"是"| I
    J -->|"否"| F
```

## 定义调度

调度定义写在 `routes/console.php`。
也可以在 `bootstrap/app.php` 的 `withSchedule` 方法中定义。

```php theme={null}
// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->call(new DeleteRecentUsers)->daily();
})
```

## 可调度的任务类型

### 调度 Artisan 命令

使用 `command` 方法调度 Artisan 命令。可以传入命令名或类名。

```php theme={null}
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;

// 通过命令名
Schedule::command('emails:send Taylor --force')->daily();

// 通过类名（参数以数组传入）
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
```

用闭包定义的 Artisan 命令可以在定义之后直接链式添加调度方法。

```php theme={null}
Artisan::command('delete:recent-users', function () {
    DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();
```

### 调度队列任务

使用 `job` 方法调度[队列任务](/zh/queues)。
无需闭包即可让任务入队，非常方便。

```php theme={null}
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new Heartbeat)->everyFiveMinutes();
```

也可以指定队列名与连接。

```php theme={null}
// 派发到 "heartbeats" 队列，使用 "sqs" 连接
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
```

### 调度 shell 命令

`exec` 方法用来执行操作系统命令。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::exec('node /home/forge/script.js')->daily();
```

### 调度闭包

`call` 方法可以调度任意 PHP 闭包。

```php theme={null}
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

也可以传入带 `__invoke` 方法的 Invocable 对象。

```php theme={null}
Schedule::call(new DeleteRecentUsers)->daily();
```

## 设置执行频率

### 常用频率方法

以下是常用的频率方法。

| 方法                            | 说明               |
| ----------------------------- | ---------------- |
| `->everySecond()`             | 每秒执行             |
| `->everyMinute()`             | 每分钟执行            |
| `->everyFiveMinutes()`        | 每 5 分钟执行         |
| `->everyFifteenMinutes()`     | 每 15 分钟执行        |
| `->everyThirtyMinutes()`      | 每 30 分钟执行        |
| `->hourly()`                  | 每小时执行            |
| `->hourlyAt(17)`              | 每小时的第 17 分钟执行    |
| `->daily()`                   | 每天 0 点执行         |
| `->dailyAt('13:00')`          | 每天 13 点执行        |
| `->twiceDaily(1, 13)`         | 每天 1 点和 13 点执行   |
| `->weekly()`                  | 每周日 0 点执行        |
| `->weeklyOn(1, '8:00')`       | 每周一 8 点执行        |
| `->monthly()`                 | 每月 1 日 0 点执行     |
| `->monthlyOn(4, '15:00')`     | 每月 4 日 15 点执行    |
| `->quarterly()`               | 每季度首日 0 点执行      |
| `->yearly()`                  | 每年 1 月 1 日 0 点执行 |
| `->timezone('Asia/Shanghai')` | 指定时区             |

### 直接使用 cron 表达式

也可以通过 `cron` 方法直接使用 cron 表达式。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->cron('0 9 * * *'); // 每天 9 点执行
```

### 频率与星期组合

可以把频率方法与星期约束组合出更精细的调度。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// 每周一 13 点执行
Schedule::call(function () {
    // ...
})->weekly()->mondays()->at('13:00');

// 工作日 8 点到 17 点每小时执行
Schedule::command('foo')
    ->weekdays()
    ->hourly()
    ->timezone('Asia/Shanghai')
    ->between('8:00', '17:00');
```

### 设置时区

`timezone` 方法可以为单个任务指定时区。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->timezone('Asia/Shanghai')
    ->at('9:00');
```

想为所有任务设置公共时区，可以配置 `config/app.php` 的 `schedule_timezone`。

```php theme={null}
// config/app.php
'schedule_timezone' => 'Asia/Shanghai',
```

<Warning>
  如果时区包含夏令时，切换时任务可能被执行两次或完全不执行。
  尽量使用 UTC。
</Warning>

## 条件约束

### when / skip

`when` 只在闭包返回 `true` 时执行任务。
`skip` 相反，返回 `true` 时跳过。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// 条件为 true 时执行
Schedule::command('emails:send')->daily()->when(function () {
    return true;
});

// 条件为 true 时跳过
Schedule::command('emails:send')->daily()->skip(function () {
    return true;
});
```

### environments

`environments` 方法限制任务只在特定环境执行。

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->environments(['staging', 'production']);
```

### 时间段约束

`between` / `unlessBetween` 可以限制执行的时间段。

```php theme={null}
// 只在 7 点到 22 点之间执行
Schedule::command('emails:send')
    ->hourly()
    ->between('7:00', '22:00');

// 23 点到 4 点之间不执行
Schedule::command('emails:send')
    ->hourly()
    ->unlessBetween('23:00', '4:00');
```

### 星期约束

| 方法                            | 说明                 |
| ----------------------------- | ------------------ |
| `->weekdays()`                | 仅工作日               |
| `->weekends()`                | 仅周末                |
| `->mondays()` ～ `->sundays()` | 特定星期               |
| `->days([0, 3])`              | 同时指定周日（0）和周三（3）等多天 |

```php theme={null}
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;

Facades\Schedule::command('emails:send')
    ->hourly()
    ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
```

## 防止重叠

默认情况下，即便上一次任务尚未结束，下一次执行也会照常开始。
使用 `withoutOverlapping` 会等上一次执行完再开始下一次。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

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

可以指定锁的过期时间（分钟），默认 24 小时。

```php theme={null}
// 10 分钟后锁自动过期
Schedule::command('emails:send')->withoutOverlapping(10);
```

<Info>
  `withoutOverlapping` 使用应用缓存来管理锁。
  如果任务因异常卡死，可以用 `schedule:clear-cache` 清除锁。

  ```shell theme={null}
  php artisan schedule:clear-cache
  ```
</Info>

## 多服务器执行控制

多台服务器同时运行调度器时，`onOneServer` 可以让任务只在其中一台上执行。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->fridays()
    ->at('17:00')
    ->onOneServer();
```

<Warning>
  使用此功能需要将应用默认缓存驱动设为 `database`、`memcached`、`dynamodb` 或 `redis`，且所有服务器连接到同一个缓存服务。
</Warning>

### onOneServer() 的分布式执行流程

```mermaid theme={null}
flowchart TD
    A["多台服务器同时执行<br>artisan schedule:run"] --> B["服务器 1"]
    A --> C["服务器 2"]
    A --> D["服务器 3"]

    B --> E["在共享缓存服务器<br>尝试获取原子锁"]
    C --> E
    D --> E

    E -->|"获取成功（第一台）"| F["执行任务"]
    E -->|"获取失败（其他服务器）"| G["跳过任务"]

    F --> H["执行完成后释放锁"]
```

## 任务分组

如果多个任务共用同样的设置，可以用 `group` 方法一次配置。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::daily()
    ->onOneServer()
    ->timezone('Asia/Shanghai')
    ->group(function () {
        Schedule::command('emails:send --force');
        Schedule::command('emails:prune');
    });
```

## 后台执行

同一时刻的多个任务默认按定义顺序串行执行。
长任务会拖慢后续任务。
`runInBackground` 会让任务在后台并行执行。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('analytics:report')
    ->daily()
    ->runInBackground();
```

<Warning>
  `runInBackground` 只能用于 `command` 和 `exec` 方法。
</Warning>

## 维护模式

应用进入维护模式时，调度任务默认不会执行。
若要强制运行，可使用 `evenInMaintenanceMode`。

```php theme={null}
Schedule::command('emails:send')->evenInMaintenanceMode();
```

### 维护模式下的处理流程

```mermaid theme={null}
flowchart TD
    A["artisan schedule:run"] --> B{"应用是否处于<br>维护模式?<br>(artisan down)"}
    B -->|"正常模式"| C["正常执行任务"]
    B -->|"维护模式"| D{"任务是否设置了<br>evenInMaintenanceMode()?"}
    D -->|"是"| C
    D -->|"否"| E["跳过任务"]
```

## 暂停调度器

无需改代码即可暂停调度器。

```shell theme={null}
# 暂停
php artisan schedule:pause

# 恢复
php artisan schedule:continue
```

即使暂停，也可以让部分任务继续运行，通过 `evenWhenPaused`。
适用于健康检查、监控等在维护期仍需运行的任务。

```php theme={null}
Schedule::command('emails:send')->evenWhenPaused();
```

## 处理输出

### 输出到文件

`sendOutputTo` 可以把任务输出写入文件。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->sendOutputTo(storage_path('logs/emails-send.log'));
```

`appendOutputTo` 则会追加到已有文件末尾。

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo(storage_path('logs/emails-send.log'));
```

### 输出到邮件

`emailOutputTo` 可以把任务输出以邮件发送。
需要事先完成 Laravel 的[邮件配置](/zh/mail)。

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('admin@example.com');
```

只在失败时发送邮件，使用 `emailOutputOnFailure`。

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->emailOutputOnFailure('admin@example.com');
```

## 任务钩子

`before` / `after` 用于在任务前后插入逻辑。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->before(function () {
        // 执行前
    })
    ->after(function () {
        // 执行后
    });
```

`onSuccess` / `onFailure` 用于成功/失败时的钩子。

```php theme={null}
use Illuminate\Support\Stringable;

Schedule::command('emails:send')
    ->daily()
    ->onSuccess(function (Stringable $output) {
        // 成功时处理
    })
    ->onFailure(function (Stringable $output) {
        // 失败时处理
    });
```

## 服务器部署

<Steps>
  <Step title="添加 cron 条目">
    只需在服务器 crontab 中加入下面一行，Laravel 调度器就会每分钟运行。

    ```shell theme={null}
    * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
    ```

    使用 `crontab -e` 命令编辑。
  </Step>

  <Step title="确认调度器运行情况">
    查看已定义任务及下一次执行时间。

    ```shell theme={null}
    php artisan schedule:list
    ```
  </Step>
</Steps>

<Tip>
  使用 [Laravel Cloud](https://cloud.laravel.com) 时，无需配置 cron 即可管理调度任务。
</Tip>

### 本地开发运行

本地开发时可以不使用 cron，而是通过 `schedule:work` 命令让调度器常驻。

```shell theme={null}
php artisan schedule:work
```

该命令在前台运行，每分钟触发一次调度，直到你 `Ctrl+C` 停止。

### 亚分钟级调度（间隔小于 1 分钟）

常规 cron 的最小单位是分钟，但 Laravel 支持秒级调度。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->everySecond();
```

如果定义了亚分钟任务，`schedule:run` 会持续运行到本分钟结束，处理所有亚分钟任务。

<Tip>
  建议把亚分钟任务委派给队列任务或后台命令。
  任务本身耗时较长时，会延迟后续亚分钟任务的执行。
</Tip>

若要在部署时中断正在运行的 `schedule:run`，可以在部署脚本中加入：

```shell theme={null}
php artisan schedule:interrupt
```

## 常用命令速查

```shell theme={null}
# 显示任务列表
php artisan schedule:list

# 手动执行调度器（cron 会调用的命令）
php artisan schedule:run

# 本地开发用常驻运行
php artisan schedule:work

# 暂停调度器
php artisan schedule:pause

# 恢复调度器
php artisan schedule:continue

# 清理重叠锁
php artisan schedule:clear-cache

# 中断亚分钟任务的执行（部署时使用）
php artisan schedule:interrupt
```


## Related topics

- [教程 - Laravel Console Starter](/zh/packages/laravel-console-starter/tutorial.md)
- [Artisan 控制台](/zh/artisan.md)
- [Bot 教程 - Laravel Bluesky](/zh/packages/laravel-bluesky/bot-tutorial.md)
- [Laravel Console Starter](/zh/packages/laravel-console-starter/index.md)
- [Laravel Cloud Hibernation(自动休眠)](/zh/blog/laravel-cloud-hibernation.md)
