connect("gifticon-redis", 6379, 1)); echo $r->ping().PHP_EOL;' * * ----------------------------------------------------------------------------- * 운영 안전장치(추천) * ----------------------------------------------------------------------------- * - 테스트용 스케줄은 APP_ENV=local|development에서만 등록되도록 제한 * - 운영에서 불필요한 스케줄 로그가 쌓이는 사고 예방 */ // ----------------------------------------------------------------------------- // 공통: 스케줄 등록 헬퍼 (cron 문자열 기반) // ----------------------------------------------------------------------------- function registerScheduleCron(string $name, string $cron, $job, array $opt = []): Event { $tz = $opt['timezone'] ?? config('app.timezone', 'Asia/Seoul'); $lock = $opt['without_overlapping'] ?? true; $event = is_callable($job) ? Schedule::call(function () use ($name, $job) { try { Log::info("[schedule:$name] start"); $job(); Log::info("[schedule:$name] done"); } catch (\Throwable $e) { Log::error("[schedule:$name] failed", [ 'err' => $e->getMessage(), ]); throw $e; } }) : Schedule::command((string) $job); // 중요: withoutOverlapping 전에 name 필수 $event->name($name); $event->cron($cron)->timezone($tz); // 운영 옵션 if ($lock) $event->withoutOverlapping(); if (!empty($opt['on_one_server'])) $event->onOneServer(); if (!empty($opt['run_in_background'])) $event->runInBackground(); // 실패 시 로그 남기기 (Laravel 12에서도 지원) // NOTE: 잡 실패를 즉시 알기 좋음 $event->onFailure(function (\Throwable $e) use ($name) { Log::error("[schedule:$name] onFailure", [ 'err' => $e->getMessage(), ]); }); return $event; } // ----------------------------------------------------------------------------- // 운영용(Production 포함): 워커/스케줄러 생존 확인용 Heartbeat // - 스케줄러가 매 분 실행 // - 큐로 잡을 던지고 // - 워커가 처리하면 로그가 찍힘 // => scheduler + redis + worker 전체 체인이 정상임을 증명 // ----------------------------------------------------------------------------- /*메일 발송 스케줄러*/ registerScheduleCron('admin_mail_dispatch_due', '* * * * *', 'admin-mail:dispatch-due --limit=20', [ 'without_overlapping' => true, 'on_one_server' => true, // 스케줄러 컨테이너가 여러개일 수 있으면 유지(1개면 있어도 무해) 'run_in_background' => true, ]); registerScheduleCron('marketing_members_stats_daily', '10 3 * * *', 'marketing:members:build-stats', [ 'without_overlapping' => true, 'on_one_server' => true, 'run_in_background' => true, ]); /*결제정보 백업 */ registerScheduleCron('payments_archive_dispatch', '0 4 * * *', 'payments:archive-dispatch --days=7 --timeout=15 --timeout_archive=60 --batch=500', [ 'without_overlapping' => true, 'on_one_server' => true, 'run_in_background' => true, ]); // ----------------------------------------------------------------------------- // Artisan 커맨드 (개발/운영 공통) - 단발성 테스트 도구 호출용 // docker exec -it gifticon-app php artisan inspire // docker exec -it gifticon-app php artisan tick // ----------------------------------------------------------------------------- Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); Artisan::command('tick', function () { Log::info('[tick] artisan command ok '.now()); $this->info('tick ok: '.now()); })->purpose('Quick artisan health check'); // ----------------------------------------------------------------------------- // 개발 전용 스케줄/테스트 스케줄 (운영에서 로그 쌓이는 사고 방지) // ----------------------------------------------------------------------------- $env = (string) config('app.env', 'production'); $isDev = in_array($env, ['local', 'development', 'dev', 'testing'], true); if ($isDev) { // "스케줄러가 실제로 돌아가는지" 확인용 - 운영에서는 불필요 // Schedule::call(function () { // Log::info('[schedule-dev] alive '.now()); // }) // ->everyMinute() // ->name('dev_scheduler_alive') // ->withoutOverlapping(); // // // 예시: 5분마다 실행 // registerScheduleCron('every_5m_log', '*/5 * * * *', function () { // Log::info('[job-dev] every 5 minutes '.now()); // }); // // // 예시: 매일 새벽 2시 10분 // registerScheduleCron('every_day_0210_log', '10 2 * * *', function () { // Log::info('[job-dev] daily 02:10 '.now()); // }); // // // 예시: 매월 1일 03:30 // registerScheduleCron('monthly_1st_0330', '30 3 1 * *', function () { // Log::info('[job-dev] monthly 1st 03:30 '.now()); // }); } // ----------------------------------------------------------------------------- // 운영용 스케줄은 여기 아래에 "명시적으로" 추가해라. // - 개발 예시와 섞으면 운영에서 실수할 가능성이 커짐. // - registerScheduleCron()으로 이름 통일해서 관리. // ----------------------------------------------------------------------------- // 예시(운영): 정산 배치 // registerScheduleCron('settlement_daily', '0 4 * * *', 'app:settlement:daily', [ // 'without_overlapping' => true, // 'on_one_server' => true, // ]);