mcpField
L
laravel-task-scheduling

laravel-task-scheduling

github.com/iserter开发4886 次安装29 周前更新
代码仓库

关于 laravel-task-scheduling

laravel-task-scheduling 是用于在 Laravel 中定义和运行定时任务的参考技能,涵盖命令、作业、闭包和 Shell 命令,以及时间限制、执行约束、监控钩子与输出管理。

主要功能

  1. 01调度命令、作业、闭包和 Shell 命令
  2. 02设置时间与环境约束
  3. 03防止任务重叠执行
  4. 04限制任务仅在单台服务器运行
  5. 05配置钩子和健康检查 ping
  6. 06管理任务输出

适用场景

  1. 01定时生成报告
  2. 02将任务限制在指定日期或环境
  3. 03防止任务在多台服务器重复运行
  4. 04记录或邮件发送任务输出

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/iserter/laravel-claude-agents --skill laravel-task-scheduling

安装后在 Claude Code 中输入/laravel-task-scheduling确认已加载。

详细介绍

任务调度

在 routes/console.php 中定义调度

<?php

use Illuminate\Support\Facades\Schedule;

// Schedule an Artisan command
Schedule::command('reports:generate')->daily();

// Schedule a job
Schedule::job(new ProcessDailyMetrics)->dailyAt('01:00');

// Schedule a closure
Schedule::call(function () {
    Cache::flush();
})->weekly();

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

调度类型

Artisan 命令
// With arguments and options
Schedule::command('emails:send', ['--force'])->daily();
Schedule::command('reports:generate --type=weekly')->sundays();

// Using command class
Schedule::command(SendEmailsCommand::class, ['--force'])->daily();
队列任务
// Dispatch a job
Schedule::job(new CleanUpExpiredTokens)->daily();

// Dispatch to a specific queue and connection
Schedule::job(new ProcessAnalytics, 'analytics', 'redis')->hourly();
闭包
Schedule::call(function () {
    DB::table('sessions')
        ->where('last_activity', '<', now()->subHours(24))
        ->delete();
})->hourly()->description('Clean expired sessions');
Shell 命令
Schedule::exec('pg_dump mydb > /backups/db.sql')->daily();

频率方法

// Common frequencies
Schedule::command('task')->everyMinute();
Schedule::command('task')->everyTwoMinutes();
Schedule::command('task')->everyFiveMinutes();
Schedule::command('task')->everyTenMinutes();
Schedule::command('task')->everyFifteenMinutes();
Schedule::command('task')->everyThirtyMinutes();
Schedule::command('task')->hourly();
Schedule::command('task')->hourlyAt(17);            // At :17 past each hour
Schedule::command('task')->everyOddHour();
Schedule::command('task')->everyTwoHours();
Schedule::command('task')->everyThreeHours();
Schedule::command('task')->everyFourHours();
Schedule::command('task')->everySixHours();
Schedule::command('task')->daily();
Schedule::command('task')->dailyAt('13:00');
Schedule::command('task')->twiceDaily(1, 13);       // At 1:00 and 13:00
Schedule::command('task')->twiceDailyAt(1, 13, 15); // At 1:15 and 13:15
Schedule::command('task')->weekly();
Schedule::command('task')->weeklyOn(1, '8:00');     // Monday at 8:00
Schedule::command('task')->monthly();
Schedule::command('task')->monthlyOn(4, '15:00');   // 4th of month at 15:00
Schedule::command('task')->twiceMonthly(1, 16);     // 1st and 16th
Schedule::command('task')->lastDayOfMonth('17:00');
Schedule::command('task')->quarterly();
Schedule::command('task')->quarterlyOn(4, '14:00'); // 4th day of quarter
Schedule::command('task')->yearly();
Schedule::command('task')->yearlyOn(6, 1, '17:00'); // June 1st at 17:00

// Cron expression
Schedule::command('task')->cron('0 */6 * * *');     // Every 6 hours

约束

时间约束
// Between specific times
Schedule::command('task')->hourly()->between('8:00', '17:00');

// Outside specific times
Schedule::command('task')->hourly()->unlessBetween('23:00', '04:00');
日期约束
Schedule::command('task')->daily()->weekdays();
Schedule::command('task')->daily()->weekends();
Schedule::command('task')->daily()->sundays();
Schedule::command('task')->daily()->mondays();
Schedule::command('task')->daily()->tuesdays();
Schedule::command('task')->daily()->wednesdays();
Schedule::command('task')->daily()->thursdays();
Schedule::command('task')->daily()->fridays();
Schedule::command('task')->daily()->saturdays();
Schedule::command('task')->daily()->days([0, 3]);   // Sunday and Wednesday
环境约束
// ✅ Only run in production
Schedule::command('analytics:process')->daily()->environments(['production']);

// ✅ Skip in testing
Schedule::command('reports:send')->daily()->environments(['production', 'staging']);
条件约束
// Run only when condition is true
Schedule::command('emails:send')
    ->daily()
    ->when(function () {
        return config('services.email.enabled');
    });

// Skip when condition is true
Schedule::command('maintenance:run')
    ->daily()
    ->skip(function () {
        return app()->isDownForMaintenance();
    });

防止重叠

// ✅ Prevent overlapping: skip if previous instance is still running
Schedule::command('reports:generate')
    ->hourly()
    ->withoutOverlapping();

// With custom expiration (default is 24 hours)
Schedule::command('reports:generate')
    ->hourly()
    ->withoutOverlapping(expiresAt: 60); // Lock expires after 60 minutes

分布式环境(onOneServer)

// ✅ Only run on a single server (requires memcached, redis, or database cache driver)
Schedule::command('reports:generate')
    ->daily()
    ->onOneServer();

// Combine with overlap prevention
Schedule::command('analytics:process')
    ->hourly()
    ->onOneServer()
    ->withoutOverlapping();

调度组

// ✅ Apply shared configuration to multiple scheduled tasks
Schedule::command('analytics:aggregate')
    ->daily()
    ->onOneServer()
    ->withoutOverlapping()
    ->emailOutputOnFailure('[email protected]');

Schedule::command('analytics:cleanup')
    ->daily()
    ->onOneServer()
    ->withoutOverlapping()
    ->emailOutputOnFailure('[email protected]');

// Group with shared config (if using a helper)
collect([
    'analytics:aggregate',
    'analytics:cleanup',
    'analytics:summary',
])->each(function ($command) {
    Schedule::command($command)
        ->daily()
        ->onOneServer()
        ->withoutOverlapping()
        ->emailOutputOnFailure('[email protected]');
});

钩子(before、after、onSuccess、onFailure、ping)

Schedule::command('reports:generate')
    ->daily()
    ->before(function () {
        Log::info('Starting report generation...');
    })
    ->after(function () {
        Log::info('Report generation complete.');
    })
    ->onSuccess(function () {
        Notification::route('slack', '#ops')
            ->notify(new ScheduledTaskSucceeded('reports:generate'));
    })
    ->onFailure(function () {
        Notification::route('slack', '#alerts')
            ->notify(new ScheduledTaskFailed('reports:generate'));
    });
URL ping(健康检查)
// Ping a URL before and after
Schedule::command('reports:generate')
    ->daily()
    ->pingBefore('https://health.example.com/start')
    ->thenPing('https://health.example.com/end');

// Ping only on success or failure
Schedule::command('reports:generate')
    ->daily()
    ->pingOnSuccess('https://health.example.com/success')
    ->pingOnFailure('https://health.example.com/failure');

// Works with services like Oh Dear, Healthchecks.io, Cronitor
Schedule::command('reports:generate')
    ->daily()
    ->pingBefore('https://hc-ping.com/your-uuid/start')
    ->thenPing('https://hc-ping.com/your-uuid');
电子邮件输出
Schedule::command('reports:generate')
    ->daily()
    ->sendOutputTo('/var/log/reports.log')
    ->emailOutputTo('[email protected]');

// Only email on failure
Schedule::command('reports:generate')
    ->daily()
    ->emailOutputOnFailure('[email protected]');

亚分钟级调度

// ✅ Run every second (Laravel 11+)
Schedule::call(function () {
    // Process real-time data
    MetricsCollector::capture();
})->everySecond();

// Every 10 seconds
Schedule::call(function () {
    QueueMonitor::check();
})->everyTenSeconds();

// Every 15 seconds
Schedule::command('queue:monitor')->everyFifteenSeconds();

// Every 20 / 30 seconds
Schedule::call(fn () => HealthCheck::ping())->everyTwentySeconds();
Schedule::call(fn () => HealthCheck::ping())->everyThirtySeconds();

输出管理

// Send output to a file
Schedule::command('reports:generate')
    ->daily()
    ->sendOutputTo('/var/log/schedule/reports.log');

// Append output to a file
Schedule::command('reports:generate')
    ->daily()
    ->appendOutputTo('/var/log/schedule/reports.log');

// Write output to storage
Schedule::command('reports:generate')
    ->daily()
    ->appendOutputTo(storage_path('logs/reports.log'));

// Discard output
Schedule::command('reports:generate')
    ->daily()
    ->sendOutputTo('/dev/null');

在维护模式下运行

// ✅ Run even during maintenance mode
Schedule::command('queue:work --stop-when-empty')
    ->everyMinute()
    ->evenInMaintenanceMode();

运行调度器

# Add to crontab (required for scheduling to work)
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1

# For sub-minute scheduling
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1

# Local development - runs scheduler in the foreground
php artisan schedule:work

# List all scheduled tasks
php artisan schedule:list

# Test a specific scheduled command
php artisan schedule:test

最佳实践

// ✅ Always give scheduled closures a description
Schedule::call(function () {
    // ...
})->daily()->description('Clean expired sessions');

// ✅ Use onOneServer for distributed deployments
Schedule::command('sitemap:generate')->daily()->onOneServer();

// ✅ Use withoutOverlapping for long-running tasks
Schedule::command('import:products')->hourly()->withoutOverlapping();

// ✅ Monitor with hooks and pings
Schedule::command('billing:charge')
    ->daily()
    ->onSuccess(fn () => Log::info('Billing completed'))
    ->onFailure(fn () => Log::critical('Billing failed'))
    ->pingOnFailure('https://alerts.example.com/billing');

// ❌ Don't schedule heavy work without overlap prevention
Schedule::command('heavy:task')->everyMinute(); // Could stack up

// ❌ Don't forget to set the timezone if needed
Schedule::command('report:daily')
    ->dailyAt('09:00')
    ->timezone('America/New_York');

检查清单

  • 在 routes/console.php 中定义调度
  • 频率方法符合实际业务需求
  • 对长时间运行的任务使用 withoutOverlapping()
  • 在多服务器部署中使用 onOneServer()
  • 在适当情况下应用环境约束
  • 配置监控钩子(before、after、onSuccess、onFailure)
  • 为关键任务设置健康检查 ping
  • 记录输出或通过电子邮件发送输出,以便调试
  • 为闭包添加说明,以便 schedule:list 显示
  • 在服务器上添加用于运行 schedule:run 的 Crontab 条目
  • 对时间敏感的任务显式设置时区
  • 对必要任务使用 evenInMaintenanceMode()

评论

支持 Markdown