Laravel Artisan 命令行工具完全指南:自定义命令与任务调度
Laravel Artisan 命令行工具完全指南
Laravel Artisan 是 Laravel 框架内置的强大命令行工具,它为开发者提供了丰富的命令来简化开发流程。本文将深入探讨 Artisan 的各个方面,从基础使用到高级自定义命令和任务调度。
1. Artisan 简介
什么是 Artisan
Artisan 是 Laravel 的命令行接口,提供了许多有用的命令来帮助开发者构建应用程序。它基于强大的 Symfony Console 组件构建。
查看可用命令
1 2 3 4 5
| php artisan list
php artisan help migrate
|
2. 常用内置命令
应用程序管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| php artisan key:generate
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
|
数据库相关
1 2 3 4 5 6 7 8 9 10 11
| php artisan migrate
php artisan migrate:rollback
php artisan migrate:refresh
php artisan db:seed
|
代码生成
1 2 3 4 5 6 7 8 9 10 11
| php artisan make:controller UserController
php artisan make:model User
php artisan make:middleware CheckAge
php artisan make:migration create_users_table
|
3. 创建自定义 Artisan 命令
生成命令类
1 2
| php artisan make:command SendEmails
|
命令类结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| <?php
namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Support\Facades\Mail; use App\Models\User;
class SendEmails extends Command {
protected $signature = 'emails:send {user : 用户ID} {--queue : 是否使用队列发送}';
protected $description = '发送邮件给指定用户';
public function __construct() { parent::__construct(); }
public function handle() { $userId = $this->argument('user'); $useQueue = $this->option('queue'); $user = User::find($userId); if (!$user) { $this->error('用户不存在!'); return 1; } $this->info("开始发送邮件给用户: {$user->name}"); $bar = $this->output->createProgressBar(100); $bar->start(); for ($i = 0; $i < 100; $i++) { usleep(50000); $bar->advance(); } $bar->finish(); $this->newLine(); if ($useQueue) { $this->info('邮件已加入队列发送'); } else { $this->info('邮件发送完成'); } return 0; } }
|
命令参数和选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected $signature = 'emails:send {user}';
protected $signature = 'emails:send {user?}';
protected $signature = 'emails:send {user=1}';
protected $signature = 'emails:send {user*}';
protected $signature = 'emails:send {user} {--queue}';
protected $signature = 'emails:send {user} {--queue=default}';
protected $signature = 'emails:send {user} {--Q|queue}';
|
用户交互
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public function handle() { $name = $this->ask('请输入您的姓名'); $password = $this->secret('请输入密码'); if ($this->confirm('确定要继续吗?')) { $this->info('继续执行...'); } $type = $this->choice( '请选择邮件类型', ['welcome', 'reminder', 'invoice'], 0 ); $this->line('普通信息'); $this->info('成功信息'); $this->comment('注释信息'); $this->question('问题信息'); $this->error('错误信息'); $this->warn('警告信息'); }
|
4. 任务调度系统
调度器简介
Laravel 的任务调度器允许你在 Laravel 应用程序内部流畅地定义命令调度,只需要在服务器上设置一个 Cron 条目。4
定义调度任务
在 routes/console.php 文件中定义调度任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
use Illuminate\Support\Facades\Schedule; use Illuminate\Support\Facades\DB; use App\Jobs\SendNewsletterJob; use App\Console\Commands\SendEmails;
Schedule::call(function () { DB::table('recent_users')->delete(); })->daily();
Schedule::command('emails:send 1 --queue')->daily();
Schedule::job(new SendNewsletterJob)->everyFiveMinutes();
Schedule::exec('node /home/forge/script.js')->daily();
|
调度频率选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Schedule::command('emails:send')->everyMinute(); Schedule::command('emails:send')->everyFiveMinutes(); Schedule::command('emails:send')->everyTenMinutes(); Schedule::command('emails:send')->everyThirtyMinutes(); Schedule::command('emails:send')->hourly(); Schedule::command('emails:send')->daily(); Schedule::command('emails:send')->weekly(); Schedule::command('emails:send')->monthly();
Schedule::command('emails:send')->dailyAt('13:00'); Schedule::command('emails:send')->twiceDaily(1, 13); Schedule::command('emails:send')->weeklyOn(1, '8:00'); Schedule::command('emails:send')->monthlyOn(4, '15:00');
Schedule::command('emails:send')->cron('0 15 * * *');
|
条件约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| Schedule::command('emails:send') ->daily() ->environments(['staging', 'production']);
Schedule::command('emails:send') ->hourly() ->between('7:00', '22:00');
Schedule::command('emails:send') ->weekdays() ->daily();
Schedule::command('emails:send') ->daily() ->when(function () { return date('d') <= 14; });
Schedule::command('emails:send') ->daily() ->skip(function () { return date('D') === 'Sun'; });
|
防止任务重叠
1 2 3 4 5 6 7 8 9
| Schedule::command('emails:send') ->everyMinute() ->withoutOverlapping();
Schedule::command('emails:send') ->everyMinute() ->withoutOverlapping(10);
|
任务输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Schedule::command('emails:send') ->daily() ->sendOutputTo('/path/to/file.log');
Schedule::command('emails:send') ->daily() ->appendOutputTo('/path/to/file.log');
Schedule::command('emails:send') ->daily() ->emailOutputTo('admin@example.com');
Schedule::command('emails:send') ->daily() ->emailOutputOnFailure('admin@example.com');
|
任务钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Schedule::command('emails:send') ->daily() ->before(function () { Log::info('开始发送邮件任务'); }) ->after(function () { Log::info('邮件任务执行完成'); }) ->onSuccess(function () { Log::info('邮件发送成功'); }) ->onFailure(function () { Log::error('邮件发送失败'); });
|
5. 启动调度器
服务器配置
在服务器上添加以下 Cron 条目:1
1
| * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
|
本地开发
在本地开发环境中,可以使用以下命令:
1 2 3 4 5 6 7 8 9 10 11
| php artisan schedule:work
php artisan schedule:run
php artisan schedule:list
php artisan schedule:clear-cache
|
6. 高级特性
单服务器任务
1 2 3 4
| Schedule::command('emails:send') ->daily() ->onOneServer();
|
后台任务
1 2 3 4
| Schedule::command('emails:send') ->daily() ->runInBackground();
|
维护模式
1 2 3 4
| Schedule::command('emails:send') ->daily() ->evenInMaintenanceMode();
|
任务分组
1 2 3 4 5 6 7 8 9 10 11
| Schedule::command('backup:database') ->daily() ->group('backups');
Schedule::command('backup:files') ->daily() ->group('backups');
|
7. 实际应用示例
数据备份命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <?php
namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\DB;
class BackupDatabase extends Command { protected $signature = 'backup:database {--compress : 是否压缩备份文件}'; protected $description = '备份数据库';
public function handle() { $this->info('开始备份数据库...'); $filename = 'backup_' . date('Y_m_d_H_i_s') . '.sql'; $compress = $this->option('compress'); $database = config('database.connections.mysql.database'); $username = config('database.connections.mysql.username'); $password = config('database.connections.mysql.password'); $host = config('database.connections.mysql.host'); $command = sprintf( 'mysqldump -h%s -u%s -p%s %s > %s', $host, $username, $password, $database, storage_path('app/backups/' . $filename) ); $result = null; $output = []; exec($command, $output, $result); if ($result === 0) { $this->info("数据库备份成功: {$filename}"); if ($compress) { $this->info('正在压缩备份文件...'); } } else { $this->error('数据库备份失败'); return 1; } return 0; } }
|
清理过期文件命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <?php
namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Support\Facades\Storage; use Carbon\Carbon;
class CleanupExpiredFiles extends Command { protected $signature = 'cleanup:expired-files {--days=30 : 保留天数} {--dry-run : 仅显示将要删除的文件}'; protected $description = '清理过期的临时文件';
public function handle() { $days = $this->option('days'); $dryRun = $this->option('dry-run'); $this->info("查找 {$days} 天前的过期文件..."); $expiredDate = Carbon::now()->subDays($days); $files = Storage::disk('temp')->allFiles(); $expiredFiles = []; foreach ($files as $file) { $lastModified = Carbon::createFromTimestamp( Storage::disk('temp')->lastModified($file) ); if ($lastModified->lt($expiredDate)) { $expiredFiles[] = $file; } } if (empty($expiredFiles)) { $this->info('没有找到过期文件'); return 0; } $this->info("找到 " . count($expiredFiles) . " 个过期文件"); if ($dryRun) { $this->table(['文件路径', '最后修改时间'], array_map(function ($file) { return [ $file, Carbon::createFromTimestamp( Storage::disk('temp')->lastModified($file) )->format('Y-m-d H:i:s') ]; }, $expiredFiles) ); $this->warn('这是预览模式,没有实际删除文件'); return 0; } $bar = $this->output->createProgressBar(count($expiredFiles)); $bar->start(); $deletedCount = 0; foreach ($expiredFiles as $file) { if (Storage::disk('temp')->delete($file)) { $deletedCount++; } $bar->advance(); } $bar->finish(); $this->newLine(); $this->info("成功删除 {$deletedCount} 个过期文件"); return 0; } }
|
8. 最佳实践
命令设计原则
- 单一职责:每个命令只做一件事
- 幂等性:多次执行相同命令应该产生相同结果
- 错误处理:适当的错误处理和状态码返回
- 用户友好:清晰的输出和进度指示
性能优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Schedule::job(new ProcessLargeDataset)->everyFiveMinutes();
public function handle() { User::chunk(1000, function ($users) { foreach ($users as $user) { } }); }
public function handle() { foreach (User::cursor() as $user) { } }
|
监控和日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public function handle() { $startTime = microtime(true); try { $this->processData(); $duration = microtime(true) - $startTime; Log::info('任务执行成功', [ 'command' => $this->signature, 'duration' => $duration, 'memory_usage' => memory_get_peak_usage(true) ]); } catch (\Exception $e) { Log::error('任务执行失败', [ 'command' => $this->signature, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return 1; } return 0; }
|
9. 测试 Artisan 命令
单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?php
namespace Tests\Feature;
use Tests\TestCase; use Illuminate\Foundation\Testing\RefreshDatabase; use App\Models\User;
class SendEmailsCommandTest extends TestCase { use RefreshDatabase; public function test_send_emails_command_success() { $user = User::factory()->create(); $this->artisan('emails:send', ['user' => $user->id]) ->expectsOutput('邮件发送完成') ->assertExitCode(0); } public function test_send_emails_command_with_invalid_user() { $this->artisan('emails:send', ['user' => 999]) ->expectsOutput('用户不存在!') ->assertExitCode(1); } public function test_send_emails_command_with_queue_option() { $user = User::factory()->create(); $this->artisan('emails:send', [ 'user' => $user->id, '--queue' => true ]) ->expectsOutput('邮件已加入队列发送') ->assertExitCode(0); } }
|
调度测试
1 2 3 4 5 6 7 8 9 10
| public function test_scheduled_commands() { $this->artisan('schedule:list') ->expectsOutput('emails:send'); $this->artisan('schedule:run') ->assertExitCode(0); }
|
总结
Laravel Artisan 是一个功能强大的命令行工具,它不仅提供了丰富的内置命令来简化开发流程,还允许开发者创建自定义命令来自动化各种任务。结合任务调度系统,Artisan 可以帮助开发者构建强大的自动化工作流。3
掌握 Artisan 的使用不仅能提高开发效率,还能帮助构建更加健壮和可维护的 Laravel 应用程序。通过合理的命令设计、适当的错误处理和完善的测试,可以确保自动化任务的可靠性和稳定性。