onQueue(self::QUEUE); } /** * 유니크 락 식별자를 반환합니다. * * 봇 요청 캐시 미스마다 디스패치되어도 큐에는 한 건만 남도록 고정 키를 사용합니다. * * @return string 유니크 락 키 */ public function uniqueId(): string { return 'seo-sitemap'; } /** * 재시도 마감 시각을 반환합니다. * * timeout(1800초) × tries(3) 만큼 재시도가 누적되는 것을 막습니다. * * @return \DateTime 재시도 마감 시각 */ public function retryUntil(): \DateTime { return now()->addMinutes(30)->toDateTime(); } /** * Sitemap 을 생성하고 캐시에 저장합니다. * * @param SitemapManager $manager Sitemap 매니저 서비스 */ public function handle(SitemapManager $manager): void { $result = $manager->regenerate($this->mode, $this->triggeredBy); if (($result['status'] ?? null) === 'disabled') { Log::info('[SEO] Sitemap generation skipped (disabled)'); return; } if (! ($result['success'] ?? false)) { throw new \RuntimeException($result['message'] ?? 'Sitemap regeneration failed'); } Log::info('[SEO] Sitemap generated and cached', [ 'size' => $result['data']['size_bytes'] ?? null, 'ttl' => $result['data']['ttl'] ?? null, ]); } /** * 잡 실패 시 로그를 기록합니다. * * @param \Throwable $exception 발생한 예외 */ public function failed(\Throwable $exception): void { // 잡 최종 실패(재시도 소진/타임아웃) 시 진행상황을 'failed' 로 고정 — 무한 running 방지 app(SitemapProgress::class)->fail($exception->getMessage()); // 최종 실패 전용 훅 — 매 시도마다 발화하는 core.seo.sitemap.after_regenerate_failed 와 달리 // 재시도가 모두 소진된 뒤 딱 한 번만 발화한다. 재시도 중 성공하면 발화하지 않으므로 // 실패 알림이 중복되거나(각 시도) 결국 성공했는데도 발송되는 일이 없다. HookManager::doAction('core.seo.sitemap.regenerate_failed_final', [ 'success' => false, 'status' => 'failed', 'message' => $exception->getMessage(), ], $this->triggeredBy); Log::error('[SEO] Sitemap generation failed', [ 'error' => $exception->getMessage(), ]); } }