Laravel: Laravel Artisan控制台与自定义命令
最后更新:2026-08-26
Artisan 是 Laravel 的"瑞士军刀"——内置 100+ 命令覆盖所有运维操作,自定义命令让重复任务一键搞定。
1. 你将学到
- 内置命令全览:migrate/cache/config/route/list
- 自定义命令创建:make:command 与签名语法
- 命令参数与选项:{argument} / {--option} 定义
- 命令调度:app/Console/Kernel.php Cron 任务
- 交互式命令:confirm/choice/ask/anticipate
2. 一个运维工程师的真实故事
(1) 痛点:每天手动执行 20 个运维任务
Charlie 每天早上要手动执行:清过期 Session、生成日度报表、同步订阅状态、发送到期提醒邮件、备份数据库……20 个任务分散在 5 个终端窗口,漏了一个就要赔钱。Bob 说:"这些不都是定时任务吗?为什么要手动?"
(2) Artisan 命令与调度的解法
Artisan 自定义命令把重复操作封装成一行命令,Schedule 自动定时执行——Charlie 每天只需看一眼执行日志。
BASH
# One command does everything
php artisan shopmetrics:daily-maintenance
# Or let the scheduler run it automatically at 2 AM
php artisan schedule:run
(3) 收益
Charlie 用 Artisan 调度后,20 个任务全自动执行,漏执行率从 5% 降到 0%。
3. 内置命令全览
(1) 常用命令分类
| 类别 | 命令 | 说明 |
|---|---|---|
| 应用 | about |
环境信息概览 |
down / up |
维护模式开关 | |
env |
显示 .env 配置 | |
| 数据库 | migrate |
执行迁移 |
migrate:rollback |
回滚迁移 | |
migrate:fresh |
重建数据库 | |
db:seed |
数据填充 | |
db:show |
数据库信息 | |
| 缓存 | cache:clear |
清除缓存 |
config:cache / clear |
配置缓存 | |
route:cache / clear |
路由缓存 | |
view:cache / clear |
视图缓存 | |
| 路由 | route:list |
列出所有路由 |
| 队列 | queue:work |
启动 Worker |
queue:failed |
查看失败任务 | |
queue:retry |
重试失败任务 | |
| 生成 | make:model |
创建模型 |
make:controller |
创建控制器 | |
make:migration |
创建迁移 | |
make:command |
创建命令 |
▶ 示例:常用 Artisan 命令速查
BASH
# App info
php artisan about
php artisan env
# Database operations
php artisan migrate --seed
php artisan db:show --counts
# Cache management
php artisan cache:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize # Cache config + route + view
# Maintenance mode
php artisan down --secret="maintenance-token" # Allow access with ?secret=
php artisan up
# Queue management
php artisan queue:work --queue=high,default
php artisan queue:failed
php artisan queue:retry all
# Route inspection
php artisan route:list --path=api
php artisan route:list --columns=method,uri,name
输出:
TEXT
📖 仅展示
# 命令执行成功
4. 自定义命令
(1) 创建命令
BASH
php artisan make:command ProcessTenantAnalytics
# Creates: app/Console/Commands/ProcessTenantAnalytics.php
(2) 命令签名语法
PHP
// app/Console/Commands/ProcessTenantAnalytics.php
class ProcessTenantAnalytics extends Command
{
// Signature syntax: {argument} {--option}
protected $signature = 'shopmetrics:analytics
{tenant? : Tenant ID or slug (optional)}
{--type=monthly : Report type (daily|weekly|monthly)}
{--force : Force re-calculation}
{--format=csv : Output format (csv|json)}';
protected $description = 'Process analytics for tenants';
public function handle(): int
{
$tenant = $this->argument('tenant');
$type = $this->option('type');
$force = $this->option('force');
if ($tenant) {
$this->processSingleTenant($tenant, $type, $force);
} else {
$this->processAllTenants($type, $force);
}
return self::SUCCESS;
}
}
(3) 签名语法规则
| 语法 | 说明 | 示例 |
|---|---|---|
{name} |
必选参数 | {tenant} |
{name?} |
可选参数 | {tenant?} |
{name=default} |
带默认值 | {type=monthly} |
{--option} |
布尔选项 | --force |
{--option=default} |
带值选项 | --format=csv |
{--O|shortcut} |
短选项 | --force|f |
▶ 示例:ShopMetrics 租户管理命令
PHP
// app/Console/Commands/ManageTenant.php
class ManageTenant extends Command
{
protected $signature = 'shopmetrics:tenant
{action : Action to perform (list|suspend|activate|stats)}
{tenant? : Tenant ID or slug}
{--with-users : Include user statistics}';
protected $description = 'Manage ShopMetrics tenants';
public function handle(): int
{
$action = $this->argument('action');
match ($action) {
'list' => $this->listTenants(),
'suspend' => $this->suspendTenant(),
'activate' => $this->activateTenant(),
'stats' => $this->showTenantStats(),
default => $this->error("Unknown action: {$action}"),
};
return self::SUCCESS;
}
private function listTenants(): void
{
$tenants = Tenant::withCount(['shops', 'users'])->get();
$this->table(
['ID', 'Name', 'Slug', 'Status', 'Shops', 'Users'],
$tenants->map(fn ($t) => [
$t->id, $t->name, $t->slug, $t->status,
$t->shops_count, $t->users_count,
])
);
}
private function suspendTenant(): void
{
$identifier = $this->argument('tenant') ?? $this->ask('Enter tenant ID or slug:');
$tenant = $this->resolveTenant($identifier);
$tenant->update(['status' => 'suspended']);
$this->info("Tenant {$tenant->name} has been suspended.");
}
private function resolveTenant(string $identifier): Tenant
{
return is_numeric($identifier)
? Tenant::findOrFail($identifier)
: Tenant::whereSlug($identifier)->firstOrFail();
}
}
输出:
TEXT
📖 仅展示
// 执行成功
5. 命令参数与选项
(1) 获取参数
PHP
// Arguments
$name = $this->argument('name'); // Single argument
$all = $this->arguments(); // All arguments as array
// Options
$force = $this->option('force'); // Single option (boolean or value)
$all = $this->options(); // All options as array
(2) 数组参数
PHP
// Signature with array argument
protected $signature = 'shopmetrics:report
{tenants* : One or more tenant IDs}
{--type=monthly}';
// Usage
php artisan shopmetrics:report 1 2 3 --type=weekly
// Access
$tenants = $this->argument('tenants'); // [1, 2, 3]
(3) 输出方法
| 方法 | 说明 | 示例输出 |
|---|---|---|
info() |
绿色信息 | ✓ Done |
error() |
红色错误 | ✗ Failed |
warn() |
黄色警告 | ⚠ Warning |
line() |
纯文本 | Plain text |
table() |
表格 | 格式化表格 |
progressBar() |
进度条 | ██████░░ 60% |
▶ 示例:ShopMetrics 数据清理命令带进度条
PHP
// app/Console/Commands/CleanupExpiredData.php
class CleanupExpiredData extends Command
{
protected $signature = 'shopmetrics:cleanup
{--days=90 : Delete data older than N days}
{--dry-run : Show what would be deleted}';
protected $description = 'Clean up expired data (old reports, expired tokens)';
public function handle(): int
{
$days = $this->option('days');
$dryRun = $this->option('dry-run');
$cutoff = now()->subDays($days);
// Clean expired tokens
$expiredTokens = Sanctum::$personalAccessTokenModel::where('last_used_at', '<', $cutoff);
$this->info(($dryRun ? 'Would delete' : 'Deleting') . " {$expiredTokens->count()} expired tokens.");
// Clean old reports from S3
$bar = $this->output->createProgressBar(Tenant::count());
$deletedFiles = 0;
Tenant::chunk(100, function ($tenants) use ($cutoff, $dryRun, &$deletedFiles, $bar) {
foreach ($tenants as $tenant) {
$files = Storage::disk('s3')->allFiles("reports/{$tenant->slug}");
foreach ($files as $file) {
if (Storage::disk('s3')->lastModified($file) < $cutoff->timestamp) {
if (!$dryRun) Storage::disk('s3')->delete($file);
$deletedFiles++;
}
}
$bar->advance();
}
});
$bar->finish();
$this->newLine();
$this->info(($dryRun ? 'Would delete' : 'Deleted') . " {$deletedFiles} old report files.");
if (!$dryRun) {
$expiredTokens->delete();
}
return self::SUCCESS;
}
}
输出:
TEXT
📖 仅展示
// 执行成功
6. 命令调度
(1) 定义调度
PHP
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('shopmetrics:analytics --type=daily')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('admin@shopmetrics.io');
Schedule::command('shopmetrics:cleanup --days=90')
->weekly()
->sundays()
->at('03:00')
->onOneServer();
Schedule::command('shopmetrics:sync-subscriptions')
->dailyAt('06:00')
->onOneServer()
->withoutOverlapping();
Schedule::job(new ProcessMonthlyAnalyticsJob)
->monthlyOn(1, '00:00')
->onOneServer();
(2) 调度频率选项
| 方法 | 频率 | 等价 Cron |
|---|---|---|
everyMinute() |
每分钟 | * * * * * |
everyFiveMinutes() |
每 5 分钟 | */5 * * * * |
hourly() |
每小时 | 0 * * * * |
daily() |
每天 0 点 | 0 0 * * * |
dailyAt('14:00') |
每天 14:00 | 0 14 * * * |
weekly() |
每周日 0 点 | 0 0 * * 0 |
monthly() |
每月 1 日 0 点 | 0 0 1 * * |
cron('...') |
自定义 Cron | 任意 |
(3) 调度约束
| 方法 | 说明 |
|---|---|
onOneServer() |
只在一台服务器执行 |
withoutOverlapping() |
不允许重叠执行 |
runInBackground() |
后台执行 |
when(Closure) |
条件执行 |
environments('prod') |
指定环境 |
emailOutputOnFailure() |
失败时邮件通知 |
▶ 示例:ShopMetrics 完整调度配置
PHP
// routes/console.php
Schedule::command('shopmetrics:analytics --type=daily')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping(60)
->emailOutputOnFailure('ops@shopmetrics.io');
Schedule::command('shopmetrics:cleanup --days=90')
->weeklyOn(Schedule::SUNDAY, '03:00')
->onOneServer();
Schedule::command('shopmetrics:send-expiring-notifications')
->dailyAt('08:00')
->when(fn () => Subscription::expiringSoon()->exists());
Schedule::command('queue:prune-failed --hours=168')
->daily();
Schedule::command('queue:prune-batches --hours=168')
->daily();
// Cron entry on server
// * * * * * cd /var/www/shopmetrics && php artisan schedule:run >> /dev/null 2>&1
输出:
TEXT
📖 仅展示
// 执行成功
7. 交互式命令
(1) 交互方法
PHP
// Ask for input
$name = $this->ask('What is the tenant name?');
// Ask with default
$email = $this->ask('Email address?', 'admin@example.com');
// Secret input (passwords)
$password = $this->secret('Enter password:');
// Confirm (yes/no)
if ($this->confirm('Do you wish to continue?', true)) {
// Default: yes
}
// Choice (single select)
$type = $this->choice(
'Select report type',
['daily', 'weekly', 'monthly'],
0 // default index
);
// Anticipate (autocomplete)
$name = $this->anticipate('Tenant name', Tenant::pluck('name')->toArray());
(2) 综合交互流程
PHP
// app/Console/Commands/SetupTenant.php
class SetupTenant extends Command
{
protected $signature = 'shopmetrics:tenant-setup';
protected $description = 'Interactive tenant setup wizard';
public function handle(): int
{
$this->info('=== ShopMetrics Tenant Setup Wizard ===');
$name = $this->ask('Tenant name');
$slug = $this->anticipate('Slug', [Str::slug($name)]);
$domain = $this->ask('Custom domain (optional)', $slug . '.shopmetrics.io');
$plan = $this->choice('Select plan', ['Starter', 'Pro', 'Enterprise'], 1);
$ownerEmail = $this->ask('Owner email');
$this->table(
['Field', 'Value'],
[['Name', $name], ['Slug', $slug], ['Domain', $domain], ['Plan', $plan], ['Owner', $ownerEmail]],
);
if (!$this->confirm('Create this tenant?', true)) {
$this->warn('Cancelled.');
return self::FAILURE;
}
$tenant = Tenant::create(compact('name', 'slug', 'domain'));
User::factory()->create([
'tenant_id' => $tenant->id,
'email' => $ownerEmail,
'role' => 'tenant_owner',
]);
$this->info("Tenant {$name} created successfully!");
return self::SUCCESS;
}
}
▶ 示例:ShopMetrics 交互式数据导出命令
PHP
// app/Console/Commands/ExportData.php
class ExportData extends Command
{
protected $signature = 'shopmetrics:export';
protected $description = 'Interactive data export tool';
public function handle(): int
{
$type = $this->choice('What to export?', [
'orders' => 'Orders',
'products' => 'Products',
'analytics' => 'Analytics Report',
]);
$tenant = $this->anticipate('Tenant (leave blank for all)', Tenant::pluck('name')->push('All')->toArray());
$format = $this->choice('Format?', ['csv', 'xlsx', 'json'], 0);
$dateFrom = $this->ask('Date from (Y-m-d, optional)');
$dateTo = $this->ask('Date to (Y-m-d, optional)');
$this->info("Exporting {$type} for {$tenant} in {$format} format...");
$query = match ($type) {
'orders' => Order::query(),
'products' => Product::query(),
'analytics' => AnalyticsReport::query(),
};
if ($tenant !== 'All') {
$tenantModel = Tenant::whereName($tenant)->firstOrFail();
$query->where('tenant_id', $tenantModel->id);
}
if ($dateFrom) $query->where('created_at', '>=', $dateFrom);
if ($dateTo) $query->where('created_at', '<=', $dateTo);
$count = $query->count();
$this->info("Found {$count} records.");
if (!$this->confirm("Export {$count} records?", true)) {
return self::FAILURE;
}
$path = ExportService::export($query, $format);
$this->info("Export saved to: {$path}");
return self::SUCCESS;
}
}
输出:
TEXT
📖 仅展示
// 执行成功
8. 综合示例:ShopMetrics 运维命令集
PHP
// ============================================
// Comprehensive: ShopMetrics Operations Commands
// Covers: signature, options, progress, scheduling, interactive
// ============================================
// app/Console/Commands/DailyMaintenance.php
class DailyMaintenance extends Command
{
protected $signature = 'shopmetrics:daily-maintenance
{--skip-analytics : Skip analytics processing}
{--skip-cleanup : Skip data cleanup}
{--notify : Send completion notification}';
protected $description = 'Run daily maintenance tasks';
public function handle(): int
{
$this->info('Starting daily maintenance...');
if (!$this->option('skip-analytics')) {
$this->processAnalytics();
}
if (!$this->option('skip-cleanup')) {
$this->cleanupExpiredData();
}
$this->syncSubscriptions();
if ($this->option('notify')) {
$this->sendCompletionNotification();
}
$this->info('Daily maintenance completed.');
return self::SUCCESS;
}
private function processAnalytics(): void
{
$this->info('Processing daily analytics...');
$tenants = Tenant::active()->get();
$bar = $this->output->createProgressBar($tenants->count());
foreach ($tenants as $tenant) {
GenerateReportJob::dispatch($tenant, 'daily', 'json');
$bar->advance();
}
$bar->finish();
$this->newLine();
}
private function cleanupExpiredData(): void
{
$this->call('shopmetrics:cleanup', ['--days' => 90]);
}
private function syncSubscriptions(): void
{
$this->call('shopmetrics:sync-subscriptions');
}
private function sendCompletionNotification(): void
{
$admin = User::where('role', 'super_admin')->first();
$admin?->notify(new DailyMaintenanceCompleted());
}
}
// Schedule it
// routes/console.php
Schedule::command('shopmetrics:daily-maintenance --notify')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('ops@shopmetrics.io');
❓ 常见问题
Q make:command 和 make:command --command 有什么区别?
A
--command=xxx 预设命令名:php artisan make:command SendEmails --command=emails:send。不加则用默认名 app:console-commands:send-emails。建议总是自定义命令名。Q 命令调度用 Cron 还是 Laravel Schedule?
A 用 Laravel Schedule。服务器只需一条 Cron:
* * * * * php artisan schedule:run,所有任务定义在代码中,版本可控、可测试。不要给每个任务加单独的 Cron。Q onOneServer 什么时候用?
A 多台服务器部署时,防止同一调度任务在多台机器上同时执行。需要 Redis 或 database 缓存驱动来协调。单台服务器可以不用。
Q 如何测试自定义命令?
A 用
$this->artisan('command:name', ['arg' => 'value']) 在测试中执行命令,断言输出:->expectsOutput('Done') 或检查数据库变更。Q 命令太慢怎么办?
A 耗时操作用队列异步处理,命令只负责 dispatch Job;大数据集用 chunk + 进度条;加
--dry-run 选项先看结果再执行。Q 命令输出怎么记录日志?
A 用
->appendOutputTo(storage_path('logs/command.log')) 在调度中追加日志;或命令内用 Log::info();也可用 emailOutputOnFailure() 失败时邮件通知。📖 小节
- Artisan 内置 100+ 命令,覆盖迁移/缓存/队列/路由等操作
- 自定义命令用签名语法定义参数和选项
- 命令调度用 Laravel Schedule 替代手动 Cron
- 交互式命令用 ask/confirm/choice 提供向导式体验
- 表格输出和进度条让命令更专业
- onOneServer/withoutOverlapping 防止多实例重复执行
📝 作业
-
基础题(⭐):创建
shopmetrics:tenant-stats命令,接受 tenant 参数,输出该租户的商店数/订单数/总收入,使用 table 格式化输出。 -
进阶题(⭐⭐):创建
shopmetrics:daily-maintenance命令,包含 analytics 处理(带进度条)+ 过期数据清理 + 订阅同步,配置 Schedule 每天凌晨 2 点自动执行。 -
挑战题(⭐⭐⭐):实现交互式
shopmetrics:tenant-setup向导命令——依次输入租户名/slug/计划/管理员邮箱,每步有验证,最后确认创建,包含 auto-complete(anticipate)租户名建议。