
# 调度

Queuety 内置了对周期性任务的支持。调度信息会存储在数据库中，并由长期运行的 `wp queuety work` 进程（或工作进程池的父进程）大约每分钟评估一次，而无需依赖 `wp_cron`。`wp queuety work --once`、`wp queuety flush` 和插件内置的 WP-Cron 回退机制都不会评估调度（[#14](https://github.com/inline0/queuety/issues/14)）；如果没有长期运行的工作进程，请自行运行 `wp queuety schedule run`。

## 间隔调度

对于简单的基于间隔的调度，请使用 `every()`。表达式可以是任何兼容 PHP 的 `DateTimeImmutable::modify()` 字符串：

```php
use Queuety\Queuety;

Queuety::schedule( 'cleanup_handler' )
    ->every( '1 hour' )
    ->on_queue( 'maintenance' );

Queuety::schedule( 'sync_inventory' )
    ->every( '30 minutes' )
    ->on_queue( 'sync' );

Queuety::schedule( 'daily_report', [ 'type' => 'summary' ] )
    ->every( '1 day' );
```

## Cron 调度

使用 `cron()` 编写标准的 5 字段 cron 表达式：

```php
// Every day at 3am
Queuety::schedule( 'nightly_cleanup' )
    ->cron( '0 3 * * *' );

// Every Monday at 9am
Queuety::schedule( 'weekly_digest' )
    ->cron( '0 9 * * 1' )
    ->on_queue( 'emails' );

// Every 15 minutes
Queuety::schedule( 'health_check' )
    ->cron( '*/15 * * * *' );
```

## 调度选项

`PendingSchedule` 构建器支持以下方法：

| 方法 | 说明 |
|---|---|
| `every( string $interval )` | 设置间隔表达式 |
| `cron( string $expression )` | 设置 cron 表达式 |
| `on_queue( string $queue )` | 设置目标队列（默认值：`'default'`） |
| `overlap( OverlapPolicy $policy )` | 设置重叠策略（默认值：`Allow`） |

调度分发前必须调用 `every()` 或 `cron()`。如果两者都未设置，则会抛出 `RuntimeException`。

## 重叠策略

当下一个调度周期触发时，如果某个计划任务仍在运行（等待中或处理中），重叠策略会决定后续行为。使用 `overlap()` 方法设置：

```php
use Queuety\Queuety;
use Queuety\Enums\OverlapPolicy;

Queuety::schedule( 'sync_inventory' )
    ->every( '5 minutes' )
    ->overlap( OverlapPolicy::Skip )
    ->on_queue( 'sync' );
```

`OverlapPolicy` 枚举包含三种情况：

| 策略 | 值 | 行为 |
|---|---|---|
| `OverlapPolicy::Allow` | `allow` | 始终分发新任务，即使上一次运行仍处于活动状态。这是默认值。 |
| `OverlapPolicy::Skip` | `skip` | 如果处理程序有任何等待中或处理中的任务，则跳过当前周期。`next_run` 会正常更新，以便调度保持原有节奏。 |
| `OverlapPolicy::Buffer` | `buffer` | 与 Skip 类似，不分发此周期，且 `next_run` 会前进到下一个常规触发时间。与 Skip 不同，`last_run` 不会更新。 |

### 允许（默认）

同一处理程序的多个实例可以并发运行。适用于幂等处理程序，因为重叠运行不会造成影响。

```php
Queuety::schedule( 'send_heartbeat_ping' )
    ->every( '1 minute' )
    ->overlap( OverlapPolicy::Allow );
```

### 跳过

如果处理程序仍在运行，则会静默跳过本次周期。适用于运行第二个实例会造成浪费或引发冲突的处理程序。

```php
Queuety::schedule( 'rebuild_search_index' )
    ->every( '10 minutes' )
    ->overlap( OverlapPolicy::Skip );
```

### 缓冲

目前其行为与 Skip 相同：处理程序仍有等待中或处理中的任务时，不分发本次周期，且 `next_run` 会前进到下一个常规触发时间。两者唯一的区别是，Buffer 会让 `last_run` 保持不变，而 Skip 会将被跳过的周期记录为上次运行时间。

```php
Queuety::schedule( 'sync_inventory' )
    ->every( '5 minutes' )
    ->overlap( OverlapPolicy::Buffer );
```

## 工作原理

创建调度时，Queuety 会将其连同表达式、处理程序、负载和计算出的下次运行时间存储在 `queuety_schedules` 表中。

每次调度器周期触发时（长期运行的工作进程中大约每分钟一次，或运行 `wp queuety schedule run` 时），调度器会检查 `next_run` 已到期的调度。对于每个到期的调度，它都会分发一个新任务，并将 `next_run` 更新为下次触发时间。

## 通过 CLI 管理调度

```bash
# List all schedules
wp queuety schedule list

# Add a recurring schedule
wp queuety schedule add cleanup_handler --every="1 hour"
wp queuety schedule add nightly_report --cron="0 3 * * *" --queue=reports

# Remove a schedule
wp queuety schedule remove cleanup_handler

# Manually trigger the scheduler
wp queuety schedule run
```

## 通过 PHP 管理调度

```php
// Get the scheduler instance
$scheduler = Queuety::scheduler();

// List all schedules
$schedules = $scheduler->list();

// Remove a schedule by handler name
$scheduler->remove( 'cleanup_handler' );

// Manually trigger a tick
$count = $scheduler->tick();
```

## 负载

调度可以包含静态负载，并将其传递给每个分发的任务：

```php
Queuety::schedule( 'generate_report', [ 'type' => 'daily', 'format' => 'pdf' ] )
    ->every( '1 day' )
    ->on_queue( 'reports' );
```

每次调度触发时，都会使用此负载分发一个新任务。
