Laravel 11 定时任务:告别 Kernel.php, 用 routes/console.php 调度 Artisan 命令
2025-03-31 13:26:13
Laravel 11 中如何调度 Artisan 命令?告别 Kernel.php
刚升级到 Laravel 11,或者刚开始用它?你可能最先注意到一个变化:以前那个熟悉的 app/Console/Kernel.php
文件不见了。这文件以前可是我们注册 Artisan 命令和定义定时任务的老地方。
那么问题来了,你创建了一个自定义命令,比如:
php artisan make:command expiration
这个命令可能是用来处理用户状态更新的,像下面这样:
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class expiration extends Command
{
/**
* 命令的名称及签名 (用于 Artisan 调用)
*/
protected $signature = 'user:expiration'; // 最好遵循小写加冒号的约定, 比如 app:user-expiration
/**
* 命令的
*/
protected $description = '每 5 分钟自动检查并更新用户状态'; // 清晰些
/**
* 执行命令的逻辑
*/
public function handle()
{
// 使用 Eloquent 更新数据,更安全高效
$updatedCount = User::where('expiration', 0)
->update(['expiration' => 1]); // update 方法接受关联数组
if ($updatedCount > 0) {
$this->info("成功更新了 {$updatedCount} 个用户的状态。");
} else {
$this->info('没有需要更新状态的用户。');
}
// 避免直接在这里输出 'successful' 等硬编码字符串,使用 $this->info(), $this->comment(), $this->error() 等方法更专业
// this->info('Command executed successfully.');
}
}
写好了命令,你希望它能像以前一样,比如每 5 分钟自动执行一次。但在 Laravel 11 里,没有了 Kernel.php
,该怎么配置呢?
别担心,Laravel 11 只是换了个地方来做这件事,而且 arguably,变得更简洁、更专注于任务调度本身了。
为啥 Kernel.php
不见了?
简单说,Laravel 团队想让项目结构更清晰。Kernel.php
过去承担了多个职责:控制台命令的注册、命令调度的定义,有时甚至还包括控制台相关的中间件。
在 Laravel 11 中:
- HTTP Kernel (
app/Http/Kernel.php
) 仍然负责处理 Web 请求的中间件栈。 - 控制台相关的逻辑 ,包括命令的注册和任务调度,现在统一放到了
routes/console.php
文件中。
这种分离使得 routes/console.php
成了处理所有命令行交互和自动化任务的核心位置,代码组织更一目了然。
在 Laravel 11 中安排定时任务
核心思路是利用 Illuminate\Support\Facades\Schedule
Facade,在 routes/console.php
文件里定义你的调度任务。
1. 定位 routes/console.php
打开你的 Laravel 11 项目,找到 routes/console.php
文件。你会看到它默认可能包含一些示例代码或注释。这里就是我们配置定时任务的新家。
2. 定义你的调度任务
要让你的 user:expiration
命令(假设其签名为 user:expiration
)每五分钟运行一次,你需要在 routes/console.php
文件里添加以下代码:
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule; // 确保引入 Schedule Facade
/*
|--------------------------------------------------------------------------
| Console Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of your Closure based console
| commands. Each Closure is bound to a command instance allowing a
| simple approach to interacting with each command's IO methods.
|
*/
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote'); // 这是 Laravel 自带的示例
// 在这里添加你的定时任务调度
Schedule::command('user:expiration')->everyFiveMinutes();
// 你可以继续添加其他需要调度的任务
// Schedule::command('another:command')->daily();
// Schedule::command('backup:run')->hourly();
代码解释:
use Illuminate\Support\Facades\Schedule;
: 首先,确保你导入了Schedule
Facade。Schedule::command('user:expiration')
: 这行代码告诉调度器,我们要调度的是哪个 Artisan 命令。这里的'user:expiration'
必须和你命令类中定义的$signature
属性完全一致。->everyFiveMinutes()
: 这个链式方法指定了任务的执行频率——每五分钟一次。Laravel 提供了非常丰富的频率选项。
3. 常见的调度频率选项
除了 everyFiveMinutes()
,还有很多开箱即用的频率设置方法,让你的调度更灵活:
->hourly()
: 每小时执行一次 (在 0 分时)。->daily()
: 每天执行一次 (在 00:00)。->dailyAt('13:00')
: 每天下午 1 点执行。->twiceDaily(1, 13)
: 每天执行两次 (在 1:00 和 13:00)。->weekly()
: 每周执行一次 (周日 00:00)。->monthly()
: 每月执行一次 (月初第一天 00:00)。->quarterly()
: 每季度执行一次 (季度初第一天 00:00)。->yearly()
: 每年执行一次 (年初第一天 00:00)。->everyMinute()
: 每分钟执行一次。->everyTenMinutes()
: 每十分钟执行一次。->everyThirtyMinutes()
: 每三十分钟执行一次。->cron('* * * * *')
: 对于更复杂的调度需求,可以直接使用 Cron 表达式。
你可以查阅 Laravel 官方文档 Task Scheduling 获取完整的频率选项列表和更高级的用法。
4. 确保调度器运行
仅仅在 routes/console.php
文件里定义了任务是不够的。你还需要告诉服务器,要定期去检查 Laravel 应用中是否有到期的任务需要执行。这通常通过配置系统的 Cron 服务来实现。
你需要向你的服务器的 Crontab 中添加一条记录。打开终端,输入 crontab -e
,然后添加类似下面的一行:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
重要说明:
/path-to-your-project
: 务必 将其替换成你 Laravel 项目的绝对路径 。* * * * *
: 这表示每分钟执行一次后面的命令。Laravel 调度器会自己判断哪些任务在当前这一分钟需要执行。php artisan schedule:run
: 这个 Artisan 命令是 Laravel 调度器的“心脏”,它负责检查所有在routes/console.php
中定义的任务,并运行那些到期的任务。>> /dev/null 2>&1
: 这部分的意思是,将命令的标准输出 (stdout
) 和错误输出 (stderr
) 都重定向到/dev/null
这个“黑洞”文件。这样做是为了防止 Cron 任务产生不必要的邮件通知。如果你在调试阶段,可以暂时去掉这部分,或者将输出重定向到指定日志文件,方便查看执行情况,例如:>> /path-to-your-project/storage/logs/cron.log 2>&1
。
安全建议:
- 运行这条 Cron 任务的用户应该是你的 Web 服务器运行的用户(例如
www-data
,nginx
,apache
)。这样可以避免因文件权限问题导致的任务失败。你可以使用sudo -u www-data crontab -e
(将www-data
替换为实际用户) 来为特定用户编辑 Crontab。
5. 验证调度设置
配置完成后,有几种方式可以验证你的设置:
-
查看已定义的任务列表:
在项目根目录下运行:php artisan schedule:list
这个命令会列出所有通过
routes/console.php
注册的定时任务、它们的执行频率以及下次预计执行时间。你应该能看到你的user:expiration
命令。 -
手动触发调度器(用于测试):
如果你不想等 Cron 真正触发,可以手动运行调度检查命令:php artisan schedule:run
这会立即执行所有当前时间点应该运行的任务。
-
单独测试某个任务(推荐用于开发):
有时你只想测试某个特定任务是否按预期工作,可以用:php artisan schedule:test user:expiration
将
user:expiration
替换成你想测试的命令签名。这个命令会立即尝试运行指定的任务,并显示其输出或遇到的任何错误,非常适合开发调试。
进阶使用技巧
掌握了基础的调度方法后,还可以利用 Laravel 调度器提供的更多高级功能:
1. 任务输出管理
默认情况下,命令的输出会被丢弃(除非你在 Cron 条目中重定向)。你可以控制输出:
// 将输出发送到文件
Schedule::command('user:expiration')
->everyFiveMinutes()
->sendOutputTo('/path/to/your/logs/expiration.log');
// 追加输出到文件
Schedule::command('user:expiration')
->everyFiveMinutes()
->appendOutputTo('/path/to/your/logs/expiration.log');
// 将输出通过邮件发送
Schedule::command('emails:send')
->daily()
->emailOutputTo('your-email@example.com');
// 仅当命令失败时才发送邮件
Schedule::command('backup:database')
->daily()
->emailOutputOnFailure('admin-email@example.com');
2. 防止任务重叠
如果一个任务执行时间可能超过它的调度间隔(比如一个复杂的报表生成任务每 5 分钟执行一次,但有时需要 6 分钟才能完成),可能会导致同一个任务的多个实例同时运行。使用 withoutOverlapping()
可以避免这种情况:
Schedule::command('report:generate')
->everyTenMinutes()
->withoutOverlapping(); // 如果上一次任务仍在运行,则跳过本次调度
// 你还可以指定锁定的超时时间 (默认 24 小时)
Schedule::command('report:generate')
->everyTenMinutes()
->withoutOverlapping(15); // 如果任务运行超过 15 分钟,锁将释放
withoutOverlapping()
通常需要你的应用配置了支持原子锁的缓存驱动(如 Redis, Memcached, database 等)。
3. 只在一台服务器上运行任务 (负载均衡环境)
当你的应用部署在多台服务器上时,通常不希望定时任务在每台服务器上都执行一遍。可以使用 onOneServer()
方法,结合缓存系统,确保任务在整个集群中只执行一次:
Schedule::command('process:payments')
->hourly()
->onOneServer();
同样,这依赖于配置好的缓存驱动。
4. 维护模式下的行为
默认情况下,处于维护模式(通过 php artisan down
启动)的应用不会执行定时任务。如果你希望某个任务即使在维护模式下也要运行,可以使用 evenInMaintenanceMode()
:
Schedule::command('heartbeat:check')
->everyMinute()
->evenInMaintenanceMode();
5. 条件化调度
你可以根据环境或其他条件决定任务是否需要被调度:
// 仅在生产环境运行
Schedule::command('deploy:notify')
->daily()
->environments(['production']);
// 使用闭包进行更复杂的判断
Schedule::command('cleanup:temp')
->hourly()
->when(function () {
// 比如,只在数据库连接正常时运行
try {
DB::connection()->getPdo();
return true;
} catch (\Exception $e) {
return false;
}
});
总结一下(不是总结陈词)
Laravel 11 中定时任务的配置方式虽然变了,从 app/Console/Kernel.php
移到了 routes/console.php
,但核心理念没变,反而更加集中和清晰。通过 Schedule
Facade 提供的丰富方法,你可以轻松定义各种频率和条件的任务调度。别忘了配置好服务器的 Cron 条目,让 php artisan schedule:run
定期执行,这是让整个调度系统动起来的关键一步。掌握了这些,你的 Laravel 应用就能像设定好的时钟一样,精确地执行自动化任务了。
相关资源(可选):
- Laravel 官方文档 - Task Scheduling
- Cron expressão Generator - 用于生成和解释 Cron 表达式的在线工具