giftcon_dev/routes/console.php
2026-02-12 13:48:48 +09:00

192 lines
7.5 KiB
PHP

<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
use Illuminate\Console\Scheduling\Event;
/**
* -----------------------------------------------------------------------------
* Gifticon Platform - Console Routes / Scheduler Registry
* -----------------------------------------------------------------------------
*
* [운영/개발 공통] 스케줄/워커/큐 동작 확인 치트시트
*
* ✅ 스케줄 등록 목록 확인
* docker exec -it gifticon-app php artisan schedule:list
*
* ✅ 스케줄러 컨테이너 프로세스 확인
* docker exec -it gifticon-scheduler ps aux
*
* ✅ 워커 컨테이너 프로세스 확인
* docker exec -it gifticon-worker ps aux
*
* ✅ 워커 하트비트 로그 확인 (워커가 큐를 소비하고 있음을 증명)
* docker exec -it gifticon-app sh -lc "tail -n 200 storage/logs/laravel.log | grep '\[worker-heartbeat\]' | tail -n 20"
*
* ✅ 큐 길이 확인 (default 큐)
* docker exec -it gifticon-redis redis-cli LLEN queues:default
*
* ✅ 실패 잡 확인
* docker exec -it gifticon-app php artisan queue:failed
*
* -----------------------------------------------------------------------------
* [장애 대응 가이드]
* -----------------------------------------------------------------------------
* 1) "메일은 되는데 워커가 이상" / "큐가 쌓이기만 함"
* - queue:failed 확인 → 실패가 쌓이면 코드/설정 오류 가능
* - LLEN queues:default 확인 → 계속 증가하면 워커가 소비 못함
* - 워커를 일시적으로 디버그 모드로 실행(컨테이너에서):
* docker exec -it gifticon-worker php artisan queue:work -vvv --tries=1 --timeout=90
*
* 2) "스케줄이 안 돈다"
* - schedule:list로 등록 여부 확인
* - gifticon-scheduler 컨테이너가 schedule:work로 떠 있는지 확인
* - 스케줄은 등록됐는데 실행 흔적이 없다면: 로그/권한/환경(APP_ENV) 분기 확인
*
* 3) "Redis 연결 이상"
* - 컨테이너 내부에서 Redis 연결 테스트:
* docker exec -it gifticon-worker php -r '$r=new Redis(); var_dump($r->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,
]);
// -----------------------------------------------------------------------------
// 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,
// ]);