diff --git a/app/Services/WeatherService.php b/app/Services/WeatherService.php index 403d3684..ec26c4d3 100644 --- a/app/Services/WeatherService.php +++ b/app/Services/WeatherService.php @@ -33,7 +33,7 @@ public function __construct() } /** - * 7일 예보 데이터 반환 (3시간 캐시) + * 7일 예보 데이터 반환 (완전한 데이터 3시간, 불완전 10분 캐시) */ public function getWeeklyForecast(): array { @@ -41,15 +41,26 @@ public function getWeeklyForecast(): array return []; } - return Cache::remember('weather_forecast_7days', 10800, function () { - try { - return $this->buildForecast(); - } catch (\Throwable $e) { - Log::warning('WeatherService: 예보 조합 실패', ['error' => $e->getMessage()]); + $cached = Cache::get('weather_forecast_7days'); + if ($cached !== null) { + return $cached; + } - return []; - } - }); + try { + $forecasts = $this->buildForecast(); + } catch (\Throwable $e) { + Log::warning('WeatherService: 예보 조합 실패', ['error' => $e->getMessage()]); + + return []; + } + + // 데이터 없는 날이 있으면 10분만 캐시 (API 재시도 유도) + $hasIncomplete = collect($forecasts)->contains( + fn ($fc) => $fc['tmn'] === null && $fc['tmx'] === null && $fc['icon'] === null + ); + Cache::put('weather_forecast_7days', $forecasts, $hasIncomplete ? 600 : 10800); + + return $forecasts; } private function buildForecast(): array @@ -190,61 +201,72 @@ private function fetchShortForecast(): array } /** - * 중기기온 API 호출 + * 중기기온 API 호출 (18시 발표 실패 시 06시 fallback) */ private function fetchMidTemperature(): array { - $body = $this->callApi(self::BASE_MID_TA, [ - 'pageNo' => 1, - 'numOfRows' => 10, - 'regId' => self::MID_TA_REG_ID, - 'tmFc' => $this->getMidBaseTmFc(), - ]); + $tmFcList = $this->getMidBaseTmFcCandidates(); - if (! $body) { - return []; + foreach ($tmFcList as $tmFc) { + $body = $this->callApi(self::BASE_MID_TA, [ + 'pageNo' => 1, + 'numOfRows' => 10, + 'regId' => self::MID_TA_REG_ID, + 'tmFc' => $tmFc, + ]); + + $items = $body ? data_get($body, 'response.body.items.item', []) : []; + if (! empty($items)) { + return $items[0]; + } } - $items = data_get($body, 'response.body.items.item', []); - - return $items[0] ?? []; + return []; } /** - * 중기날씨 API 호출 + * 중기날씨 API 호출 (18시 발표 실패 시 06시 fallback) */ private function fetchMidLandForecast(): array { - $body = $this->callApi(self::BASE_MID_LAND, [ - 'pageNo' => 1, - 'numOfRows' => 10, - 'regId' => self::MID_LAND_REG_ID, - 'tmFc' => $this->getMidBaseTmFc(), - ]); + $tmFcList = $this->getMidBaseTmFcCandidates(); - if (! $body) { - return []; + foreach ($tmFcList as $tmFc) { + $body = $this->callApi(self::BASE_MID_LAND, [ + 'pageNo' => 1, + 'numOfRows' => 10, + 'regId' => self::MID_LAND_REG_ID, + 'tmFc' => $tmFc, + ]); + + $items = $body ? data_get($body, 'response.body.items.item', []) : []; + if (! empty($items)) { + return $items[0]; + } } - $items = data_get($body, 'response.body.items.item', []); - - return $items[0] ?? []; + return []; } /** - * 중기예보 발표시각 (06시/18시 기준) + * 중기예보 발표시각 후보 목록 (우선순위순, fallback 포함) */ - private function getMidBaseTmFc(): string + private function getMidBaseTmFcCandidates(): array { $now = Carbon::now(); + $today = $now->format('Ymd'); + $yesterday = $now->copy()->subDay()->format('Ymd'); if ($now->hour >= 18) { - return $now->format('Ymd').'1800'; + // 18시 발표 → 06시 발표 → 전일 18시 발표 + return [$today.'1800', $today.'0600', $yesterday.'1800']; } elseif ($now->hour >= 6) { - return $now->format('Ymd').'0600'; + // 06시 발표 → 전일 18시 발표 + return [$today.'0600', $yesterday.'1800']; } - return $now->copy()->subDay()->format('Ymd').'1800'; + // 자정~06시: 전일 18시 발표 → 전일 06시 발표 + return [$yesterday.'1800', $yesterday.'0600']; } /**