You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Laravel中自定义Cron以按预设时间执行任务?

问题描述

我创建了一张名为schedule_demo的数据表,结构如下:

id       name     schedule_date          tag            status
1       test_1    2023-01-28 09:16:12    test_1_sch       1
2       test_2    2023-01-28 11:30:30    test_2_sch       1
3       test_3    2023-01-28 14:50:20    test_3_sch       1

在Commands/ScheduleDemo.php中编写了命令:

protected $signature = 'run:scheduleDemo';
public function handle()
{
    $scheduleData = ScheduleDemo::where('status', '=', 1)
             ->where('scheduleDate', '=', strtotime('now'))->get();
    if (count($scheduleData) > 0) {
        foreach ($scheduleData as $value) {
            $response = $this->client->request('POST', 'https://example.com', [
             'headers' => [
                'Content-Type' => 'application/x-www-form-urlencoded',
             ],
             'form_params' => ['from' => $value->name, 'tag' => $value->tag]
           ]);
           $response = json_decode($response->getBody()->getContents());
           if ($response && $response['error'] == 0) {
             $value->status = 2;
             $value->save();
           } else {
            echo "\nFAILED\n";
         }
        }
    } else {
        echo "\nSCHEDULE IS EMPTY\n";
    }
}

在Kernel.php中配置了任务调度:

protected $commands = [
    'App\Console\Commands\ScheduleDemo'
];
protected function schedule(Schedule $schedule)
{        
    $schedule->command('run:scheduleDemo')
        ->everyFiveMinutes()
        ->withoutOverlapping()
        ->runInBackground();
}

同时在crontab中添加了:

* * * * * php /job/project/artisan schedule:run >> /dev/null 2>&1

目前Cron任务每5分钟执行一次并成功调用API,但我希望改为当schedule_date等于当前时间时自动触发任务,而非固定间隔执行。例如当schedule_date为2023-01-28 09:16:12且等于当前时间时,自动执行任务。请问是否有实现方法?


实现方案

要实现按schedule_date精准触发任务,核心是让调度器每分钟检查一次,在命令内部匹配当前时间对应的任务,具体调整如下:

1. 调整调度执行频率

修改Kernel.php中的调度配置,改为每分钟执行一次,确保能精准检查到每分钟的任务:

protected function schedule(Schedule $schedule)
{        
    $schedule->command('run:scheduleDemo')
        ->everyMinute() // 改为每分钟执行
        ->withoutOverlapping()
        ->runInBackground();
}

crontab配置保持* * * * *不变,保证Laravel调度器每分钟都能启动检查。

2. 修复时间匹配逻辑

原代码中strtotime('now')精确到秒,容易因为秒数差异错过任务,需按分钟级匹配:

场景1:schedule_date是datetime类型(数据库存储为时间字符串)

直接用数据库函数匹配当前分钟:

$scheduleData = ScheduleDemo::where('status', 1)
    ->whereRaw("DATE_FORMAT(schedule_date, '%Y-%m-%d %H:%i') = DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i')")
    ->get();

场景2:scheduleDate是时间戳字段

将当前时间取整到分钟,再匹配:

// 取当前分钟的起始时间戳(去掉秒数)
$currentMinute = floor(time() / 60) * 60;
$scheduleData = ScheduleDemo::where('status', 1)
    ->where('scheduleDate', $currentMinute)
    ->get();

3. 优化并发冲突问题

为避免同一任务被多个进程重复执行,增加原子性状态更新:

foreach ($scheduleData as $value) {
    // 原子更新状态为处理中,防止并发执行
    $updated = ScheduleDemo::where('id', $value->id)
        ->where('status', 1)
        ->update(['status' => 3]); // 3代表处理中
    
    if (!$updated) {
        continue; // 已被其他进程处理,跳过
    }

    // 调用API逻辑
    try {
        $response = $this->client->request('POST', 'https://example.com', [
            'headers' => [
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
            'form_params' => ['from' => $value->name, 'tag' => $value->tag]
        ]);
        $responseData = json_decode($response->getBody()->getContents());

        if ($responseData && $responseData->error == 0) {
            ScheduleDemo::where('id', $value->id)->update(['status' => 2]); // 执行成功
        } else {
            echo "\nFAILED: {$value->name}\n";
            ScheduleDemo::where('id', $value->id)->update(['status' => 1]); // 失败重置状态
        }
    } catch (\Exception $e) {
        echo "\nERROR: {$value->name} - {$e->getMessage()}\n";
        ScheduleDemo::where('id', $value->id)->update(['status' => 1]); // 异常重置状态
    }
}

注意:原代码中json_decode默认返回对象,$response['error']会报错,需改为$responseData->error。

4. 可选:用队列异步处理API调用

如果API耗时较长,建议将执行逻辑放到队列,提升调度器效率:

// 在handle方法中
foreach ($scheduleData as $value) {
    $updated = ScheduleDemo::where('id', $value->id)
        ->where('status', 1)
        ->update(['status' => 3]);
    
    if ($updated) {
        dispatch(new CallScheduleApiJob($value)); // 自定义队列任务
    }
}

之后创建CallScheduleApiJob类,在handle方法中编写API调用逻辑即可。


内容的提问来源于stack exchange,提问作者Miedkes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 22:50:23