diff --git a/build.gradle b/build.gradle index c4f67340..bf983205 100644 --- a/build.gradle +++ b/build.gradle @@ -81,6 +81,8 @@ dependencies { implementation 'org.flywaydb:flyway-mysql' implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' + // circuit breaker dependencies + implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' } dependencyManagement { diff --git a/src/main/java/sevenstar/marineleisure/activity/service/ActivityService.java b/src/main/java/sevenstar/marineleisure/activity/service/ActivityService.java index eabe14b0..1c65e2dc 100644 --- a/src/main/java/sevenstar/marineleisure/activity/service/ActivityService.java +++ b/src/main/java/sevenstar/marineleisure/activity/service/ActivityService.java @@ -29,7 +29,9 @@ import sevenstar.marineleisure.global.enums.ActivityCategory; import sevenstar.marineleisure.global.enums.TimePeriod; import sevenstar.marineleisure.spot.domain.OutdoorSpot; +import sevenstar.marineleisure.spot.dto.SpotPreviewReadResponse; import sevenstar.marineleisure.spot.repository.OutdoorSpotRepository; +import sevenstar.marineleisure.spot.service.SpotService; @Service @RequiredArgsConstructor @@ -42,6 +44,8 @@ public class ActivityService { private final ScubaRepository scubaRepository; private final SurfingRepository surfingRepository; + private final SpotService spotService; + @Transactional(readOnly = true) public Map getActivitySummary(BigDecimal latitude, BigDecimal longitude, boolean global) { @@ -55,72 +59,79 @@ public Map getActivitySummary(BigDecimal latitu private Map getLocalActivitySummary(BigDecimal latitude, BigDecimal longitude) { Map responses = new HashMap<>(); - Fishing fishingBySpot = null; - Mudflat mudflatBySpot = null; - Surfing surfingBySpot = null; - Scuba scubaBySpot = null; - - LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); - LocalDateTime endOfDay = startOfDay.plusDays(1); - - List outdoorSpotList = outdoorSpotRepository.findByCoordinates(latitude, longitude, 10); - - while (fishingBySpot == null || mudflatBySpot == null || surfingBySpot == null || scubaBySpot == null) { - - OutdoorSpot currentSpot; - Long currentSpotId; - - try { - currentSpot = outdoorSpotList.removeFirst(); - currentSpotId = currentSpot.getId(); - } catch (Exception e) { - break; - } - - if (fishingBySpot == null) { - Optional fishingResult = fishingRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( - currentSpotId, startOfDay, endOfDay); - - if (fishingResult.isPresent()) { - fishingBySpot = fishingResult.get(); - responses.put("Fishing", - new ActivitySummaryResponse(currentSpot.getName(), fishingResult.get().getTotalIndex())); - } - } - - if (mudflatBySpot == null) { - Optional mudflatResult = mudflatRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( - currentSpotId, startOfDay, endOfDay); - - if (mudflatResult.isPresent()) { - mudflatBySpot = mudflatResult.get(); - responses.put("Mudflat", - new ActivitySummaryResponse(currentSpot.getName(), mudflatResult.get().getTotalIndex())); - } - } - - if (surfingBySpot == null) { - Optional surfingResult = surfingRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( - currentSpotId, startOfDay, endOfDay); - - if (surfingResult.isPresent()) { - surfingBySpot = surfingResult.get(); - responses.put("Surfing", - new ActivitySummaryResponse(currentSpot.getName(), surfingResult.get().getTotalIndex())); - } - } - - if (scubaBySpot == null) { - Optional scubaResult = scubaRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( - currentSpotId, startOfDay, endOfDay); - - if (scubaResult.isPresent()) { - scubaBySpot = scubaResult.get(); - responses.put("Scuba", - new ActivitySummaryResponse(currentSpot.getName(), scubaResult.get().getTotalIndex())); - } - } - } + SpotPreviewReadResponse preview = spotService.preview(latitude.floatValue(), longitude.floatValue()); + responses.put("Fishing", + new ActivitySummaryResponse(preview.fishing().getName(), preview.fishing().getTotalIndex())); + responses.put("Mudflat",new ActivitySummaryResponse(preview.mudflat().getName(), preview.mudflat().getTotalIndex())); + responses.put("Surfing", new ActivitySummaryResponse(preview.surfing().getName(), preview.surfing().getTotalIndex())); + responses.put("Scuba", new ActivitySummaryResponse(preview.scuba().getName(), preview.scuba().getTotalIndex())); + + // Fishing fishingBySpot = null; + // Mudflat mudflatBySpot = null; + // Surfing surfingBySpot = null; + // Scuba scubaBySpot = null; + // + // LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + // LocalDateTime endOfDay = startOfDay.plusDays(1); + // + // List outdoorSpotList = outdoorSpotRepository.findByCoordinates(latitude, longitude, 10); + // + // while (fishingBySpot == null || mudflatBySpot == null || surfingBySpot == null || scubaBySpot == null) { + // + // OutdoorSpot currentSpot; + // Long currentSpotId; + // + // try { + // currentSpot = outdoorSpotList.removeFirst(); + // currentSpotId = currentSpot.getId(); + // } catch (Exception e) { + // break; + // } + // + // if (fishingBySpot == null) { + // Optional fishingResult = fishingRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( + // currentSpotId, startOfDay, endOfDay); + // + // if (fishingResult.isPresent()) { + // fishingBySpot = fishingResult.get(); + // responses.put("Fishing", + // new ActivitySummaryResponse(currentSpot.getName(), fishingResult.get().getTotalIndex())); + // } + // } + // + // if (mudflatBySpot == null) { + // Optional mudflatResult = mudflatRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( + // currentSpotId, startOfDay, endOfDay); + // + // if (mudflatResult.isPresent()) { + // mudflatBySpot = mudflatResult.get(); + // responses.put("Mudflat", + // new ActivitySummaryResponse(currentSpot.getName(), mudflatResult.get().getTotalIndex())); + // } + // } + // + // if (surfingBySpot == null) { + // Optional surfingResult = surfingRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( + // currentSpotId, startOfDay, endOfDay); + // + // if (surfingResult.isPresent()) { + // surfingBySpot = surfingResult.get(); + // responses.put("Surfing", + // new ActivitySummaryResponse(currentSpot.getName(), surfingResult.get().getTotalIndex())); + // } + // } + // + // if (scubaBySpot == null) { + // Optional scubaResult = scubaRepository.findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByCreatedAtDesc( + // currentSpotId, startOfDay, endOfDay); + // + // if (scubaResult.isPresent()) { + // scubaBySpot = scubaResult.get(); + // responses.put("Scuba", + // new ActivitySummaryResponse(currentSpot.getName(), scubaResult.get().getTotalIndex())); + // } + // } + // } return responses; } @@ -128,17 +139,12 @@ private Map getLocalActivitySummary(BigDecimal private Map getGlobalActivitySummary() { Map responses = new HashMap<>(); - LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); - LocalDateTime endOfDay = startOfDay.plusDays(1); + LocalDate now = LocalDate.now(); - Optional fishingResult = fishingRepository.findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc( - startOfDay, endOfDay); - Optional mudflatResult = mudflatRepository.findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc( - startOfDay, endOfDay); - Optional surfingResult = surfingRepository.findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc( - startOfDay, endOfDay); - Optional scubaResult = scubaRepository.findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc( - startOfDay, endOfDay); + Optional fishingResult = fishingRepository.findBestTotaIndexFishing(now); + Optional mudflatResult = mudflatRepository.findBestTotaIndexMudflat(now); + Optional surfingResult = surfingRepository.findBestTotaIndexSurfing(now); + Optional scubaResult = scubaRepository.findBestTotaIndexScuba(now); if (fishingResult.isPresent()) { Fishing fishing = fishingResult.get(); @@ -225,4 +231,6 @@ public ActivityWeatherResponse getWeatherBySpot(Float latitude, Float longitude) fishing.getSeaTempMax().toString() ); } + + } diff --git a/src/main/java/sevenstar/marineleisure/alert/controller/AlertController.java b/src/main/java/sevenstar/marineleisure/alert/controller/AlertController.java index ea2e6707..25e59ff3 100644 --- a/src/main/java/sevenstar/marineleisure/alert/controller/AlertController.java +++ b/src/main/java/sevenstar/marineleisure/alert/controller/AlertController.java @@ -1,6 +1,8 @@ package sevenstar.marineleisure.alert.controller; import java.util.List; +import java.util.Map; +import java.util.Set; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -8,6 +10,7 @@ import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; +import sevenstar.marineleisure.alert.dto.response.JellyfishResponse; import sevenstar.marineleisure.alert.dto.response.JellyfishResponseDto; import sevenstar.marineleisure.alert.dto.vo.JellyfishDetailVO; import sevenstar.marineleisure.alert.mapper.AlertMapper; @@ -26,9 +29,11 @@ public class AlertController { * @return 해파리 발생 관련 정보 */ @GetMapping("/jellyfish") - public ResponseEntity> getJellyfishList() { + public ResponseEntity> getJellyfishList() { List items = jellyfishService.search(); - JellyfishResponseDto result = alertMapper.toResponseDto(items); + Map> map = jellyfishService.convert(items); + + JellyfishResponse result = alertMapper.toResponseDto(items.getFirst().getReportDate(), map); return BaseResponse.success(result); } diff --git a/src/main/java/sevenstar/marineleisure/alert/dto/response/JellyfishResponse.java b/src/main/java/sevenstar/marineleisure/alert/dto/response/JellyfishResponse.java new file mode 100644 index 00000000..cd13f4b4 --- /dev/null +++ b/src/main/java/sevenstar/marineleisure/alert/dto/response/JellyfishResponse.java @@ -0,0 +1,17 @@ +package sevenstar.marineleisure.alert.dto.response; + +import java.time.LocalDate; +import java.util.Map; +import java.util.Set; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JellyfishResponse { + private LocalDate reposrtDate; + private Map> jellyfish; +} diff --git a/src/main/java/sevenstar/marineleisure/alert/mapper/AlertMapper.java b/src/main/java/sevenstar/marineleisure/alert/mapper/AlertMapper.java index dea78b3a..d04f1afb 100644 --- a/src/main/java/sevenstar/marineleisure/alert/mapper/AlertMapper.java +++ b/src/main/java/sevenstar/marineleisure/alert/mapper/AlertMapper.java @@ -1,39 +1,19 @@ package sevenstar.marineleisure.alert.mapper; import java.time.LocalDate; -import java.util.List; +import java.util.Map; +import java.util.Set; import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; -import sevenstar.marineleisure.alert.dto.response.JellyfishResponseDto; -import sevenstar.marineleisure.alert.dto.vo.JellyfishDetailVO; -import sevenstar.marineleisure.alert.dto.vo.JellyfishRegionVO; -import sevenstar.marineleisure.alert.dto.vo.JellyfishSpeciesVO; -import sevenstar.marineleisure.global.enums.DensityLevel; -import sevenstar.marineleisure.global.enums.ToxicityLevel; +import sevenstar.marineleisure.alert.dto.response.JellyfishResponse; @Component @RequiredArgsConstructor public class AlertMapper { - public JellyfishResponseDto toResponseDto(List detailList) { - if (detailList.isEmpty()) { - return null; - } - LocalDate reportDate = detailList.get(0).getReportDate(); - - List regions = detailList.stream() - .map(detail -> new JellyfishRegionVO( - detail.getRegion(), - new JellyfishSpeciesVO( - detail.getSpecies(), - ToxicityLevel.valueOf(detail.getToxicity()).getDescription(), - DensityLevel.valueOf(detail.getDensityType()).getDescription() - ) - )) - .toList(); - - return new JellyfishResponseDto(reportDate, regions); + public JellyfishResponse toResponseDto(LocalDate reportDate, Map> map) { + return new JellyfishResponse(reportDate, map); } } diff --git a/src/main/java/sevenstar/marineleisure/alert/service/JellyfishService.java b/src/main/java/sevenstar/marineleisure/alert/service/JellyfishService.java index bddc1da2..4012a3a9 100644 --- a/src/main/java/sevenstar/marineleisure/alert/service/JellyfishService.java +++ b/src/main/java/sevenstar/marineleisure/alert/service/JellyfishService.java @@ -3,8 +3,13 @@ import java.io.File; import java.io.IOException; import java.time.LocalDate; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; @@ -56,7 +61,7 @@ public JellyfishSpecies searchByName(String name) { /** * 웹에서 크롤링 해 Pdf를 DB에 적재합니다. */ - // @Scheduled(cron = "0 0 0 ? * FRI") + @Scheduled(cron = "0 0 0 ? * FRI") // 금요일 00시에 동작합니다. @Transactional public void updateLatestReport() { @@ -104,4 +109,15 @@ public void updateLatestReport() { } } + public Map> convert(List jellyfish) { + Map> map = new HashMap<>(); + for (JellyfishDetailVO detail : jellyfish) { + if (map.containsKey(detail.getSpecies())) { + map.get(detail.getSpecies()).add(detail.getRegion()); + } else { + map.put(detail.getSpecies(), new HashSet<>(List.of(detail.getRegion()))); + } + } + return map; + } } diff --git a/src/main/java/sevenstar/marineleisure/alert/util/JellyfishExtractor.java b/src/main/java/sevenstar/marineleisure/alert/util/JellyfishExtractor.java index 2b71f5b8..c96ec62c 100644 --- a/src/main/java/sevenstar/marineleisure/alert/util/JellyfishExtractor.java +++ b/src/main/java/sevenstar/marineleisure/alert/util/JellyfishExtractor.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import sevenstar.marineleisure.alert.dto.vo.ParsedJellyfishVO; @@ -21,6 +22,7 @@ public class JellyfishExtractor { private final OpenAiChatModel chatModel; private final ObjectMapper objectMapper; + @CircuitBreaker(name = "openai-api", fallbackMethod = "fallbackExtractJellyfishData") public List extractJellyfishData(String text) { try { String instruction = """ @@ -71,4 +73,9 @@ public List extractJellyfishData(String text) { return List.of(); } } + + public List fallbackExtractJellyfishData(String text, Throwable t) { + log.error("OpenAI API 호출에 실패하여 fallback 메서드가 실행되었습니다. message: {}", t.getMessage()); + return List.of(); + } } \ No newline at end of file diff --git a/src/main/java/sevenstar/marineleisure/favorite/dto/vo/FavoriteItemVO.java b/src/main/java/sevenstar/marineleisure/favorite/dto/vo/FavoriteItemVO.java index 0372a84d..da0672ab 100644 --- a/src/main/java/sevenstar/marineleisure/favorite/dto/vo/FavoriteItemVO.java +++ b/src/main/java/sevenstar/marineleisure/favorite/dto/vo/FavoriteItemVO.java @@ -12,5 +12,6 @@ * @param notification : 알림 여부 */ @Builder -public record FavoriteItemVO(Long id, String name, ActivityCategory category, String location, boolean notification) { +public record FavoriteItemVO(Long spotId, Long id, String name, ActivityCategory category, String location, + boolean notification) { } diff --git a/src/main/java/sevenstar/marineleisure/favorite/repository/FavoriteRepository.java b/src/main/java/sevenstar/marineleisure/favorite/repository/FavoriteRepository.java index bfa00318..5b94e480 100644 --- a/src/main/java/sevenstar/marineleisure/favorite/repository/FavoriteRepository.java +++ b/src/main/java/sevenstar/marineleisure/favorite/repository/FavoriteRepository.java @@ -17,6 +17,7 @@ public interface FavoriteRepository extends JpaRepository { @Query(""" SELECT new sevenstar.marineleisure.favorite.dto.vo.FavoriteItemVO( + os.id, fs.id, os.name, os.category, diff --git a/src/main/java/sevenstar/marineleisure/forecast/repository/FishingRepository.java b/src/main/java/sevenstar/marineleisure/forecast/repository/FishingRepository.java index f4c14364..e5d53019 100644 --- a/src/main/java/sevenstar/marineleisure/forecast/repository/FishingRepository.java +++ b/src/main/java/sevenstar/marineleisure/forecast/repository/FishingRepository.java @@ -118,6 +118,15 @@ Optional findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessT LocalDateTime endDateTime ); + @Query(value = """ + SELECT * + FROM fishing_forecast f + WHERE f.forecast_date = :forecastDate + ORDER BY f.total_index DESC + LIMIT 1 + """,nativeQuery = true) + Optional findBestTotaIndexFishing(@Param("forecastDate") LocalDate forecastDate); + Optional findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc(LocalDateTime start, LocalDateTime end); diff --git a/src/main/java/sevenstar/marineleisure/forecast/repository/MudflatRepository.java b/src/main/java/sevenstar/marineleisure/forecast/repository/MudflatRepository.java index 3c2465ff..53de949c 100644 --- a/src/main/java/sevenstar/marineleisure/forecast/repository/MudflatRepository.java +++ b/src/main/java/sevenstar/marineleisure/forecast/repository/MudflatRepository.java @@ -11,6 +11,7 @@ import org.springframework.data.repository.query.Param; import jakarta.transaction.Transactional; +import sevenstar.marineleisure.forecast.domain.Fishing; import sevenstar.marineleisure.forecast.domain.Mudflat; import sevenstar.marineleisure.spot.repository.ActivityRepository; @@ -78,6 +79,15 @@ Optional findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessT LocalDateTime endDateTime ); + @Query(value = """ + SELECT * + FROM mudflat_forecast m + WHERE m.forecast_date = :forecastDate + ORDER BY m.total_index DESC + LIMIT 1 + """,nativeQuery = true) + Optional findBestTotaIndexMudflat(@Param("forecastDate") LocalDate forecastDate); + Optional findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc(LocalDateTime start, LocalDateTime end); Optional findBySpotIdAndCreatedAtBeforeOrderByCreatedAtDesc(Long spotId, LocalDateTime createdAtBefore); diff --git a/src/main/java/sevenstar/marineleisure/forecast/repository/ScubaRepository.java b/src/main/java/sevenstar/marineleisure/forecast/repository/ScubaRepository.java index e24d2cb4..0a567aaa 100644 --- a/src/main/java/sevenstar/marineleisure/forecast/repository/ScubaRepository.java +++ b/src/main/java/sevenstar/marineleisure/forecast/repository/ScubaRepository.java @@ -12,6 +12,7 @@ import jakarta.transaction.Transactional; import sevenstar.marineleisure.forecast.domain.Scuba; +import sevenstar.marineleisure.forecast.domain.Surfing; import sevenstar.marineleisure.spot.repository.ActivityRepository; public interface ScubaRepository extends ActivityRepository { @@ -75,6 +76,15 @@ void updateSunriseAndSunset( @Param("forecastDate") LocalDate forecastDate ); + @Query(value = """ + SELECT * + FROM scuba_forecast s + WHERE s.forecast_date = :forecastDate + ORDER BY s.total_index DESC + LIMIT 1 + """,nativeQuery = true) + Optional findBestTotaIndexScuba(@Param("forecastDate") LocalDate forecastDate); + Optional findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc(LocalDateTime start, LocalDateTime end); Optional findBySpotIdAndCreatedAtBeforeOrderByCreatedAtDesc(Long spotId, LocalDateTime createdAtBefore); diff --git a/src/main/java/sevenstar/marineleisure/forecast/repository/SurfingRepository.java b/src/main/java/sevenstar/marineleisure/forecast/repository/SurfingRepository.java index 65f0d3f5..b049ca5f 100644 --- a/src/main/java/sevenstar/marineleisure/forecast/repository/SurfingRepository.java +++ b/src/main/java/sevenstar/marineleisure/forecast/repository/SurfingRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.repository.query.Param; import jakarta.transaction.Transactional; +import sevenstar.marineleisure.forecast.domain.Fishing; import sevenstar.marineleisure.forecast.domain.Surfing; import sevenstar.marineleisure.spot.repository.ActivityRepository; @@ -77,6 +78,15 @@ Optional findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessT LocalDateTime endDateTime ); + @Query(value = """ + SELECT * + FROM surfing_forecast s + WHERE s.forecast_date = :forecastDate + ORDER BY s.total_index DESC + LIMIT 1 + """,nativeQuery = true) + Optional findBestTotaIndexSurfing(@Param("forecastDate") LocalDate forecastDate); + Optional findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByTotalIndexDesc(LocalDateTime start, LocalDateTime end); Optional findBySpotIdAndCreatedAtBeforeOrderByCreatedAtDesc(Long spotId, LocalDateTime createdAtBefore); diff --git a/src/main/java/sevenstar/marineleisure/global/api/kakao/KakaoApiClient.java b/src/main/java/sevenstar/marineleisure/global/api/kakao/KakaoApiClient.java index d9b2a23a..96d21902 100644 --- a/src/main/java/sevenstar/marineleisure/global/api/kakao/KakaoApiClient.java +++ b/src/main/java/sevenstar/marineleisure/global/api/kakao/KakaoApiClient.java @@ -1,8 +1,8 @@ package sevenstar.marineleisure.global.api.kakao; import java.net.URI; +import java.util.List; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; @@ -11,18 +11,22 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import sevenstar.marineleisure.global.api.kakao.dto.RegionResponse; import sevenstar.marineleisure.global.utils.UriBuilder; @Component @RequiredArgsConstructor +@Slf4j public class KakaoApiClient { @Value("${kakao.map.uri}") private String kakaoMapUri; private final RestTemplate kakaoRestTemplate; + @CircuitBreaker(name = "kakao-api", fallbackMethod = "fallbackKakaoApi") public ResponseEntity get(float latitude, float longitude) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("y", String.valueOf(latitude)); @@ -33,4 +37,13 @@ public ResponseEntity get(float latitude, float longitude) { return kakaoRestTemplate.exchange(uri, HttpMethod.GET, null, RegionResponse.class); } + public ResponseEntity fallbackKakaoApi(float latitude, float longitude, Throwable t) { + log.error("Kakao API 호출에 실패하여 fallback 메서드가 실행되었습니다. message: {}", t.getMessage()); + RegionResponse fallbackResponse = new RegionResponse(); + RegionResponse.Document fallbackDocument = new RegionResponse.Document(); + fallbackDocument.setAddress_name("알 수 없는 지역"); + fallbackResponse.setDocuments(List.of(fallbackDocument)); + return ResponseEntity.ok(fallbackResponse); + } + } diff --git a/src/main/java/sevenstar/marineleisure/global/api/scheduler/SchedulerService.java b/src/main/java/sevenstar/marineleisure/global/api/scheduler/SchedulerService.java index 8dd6f913..7ab75df6 100644 --- a/src/main/java/sevenstar/marineleisure/global/api/scheduler/SchedulerService.java +++ b/src/main/java/sevenstar/marineleisure/global/api/scheduler/SchedulerService.java @@ -27,19 +27,6 @@ public class SchedulerService { private final Executor taskExecutor; - // public SchedulerService( - // KhoaApiService khoaApiService, - // OpenMeteoService openMeteoService, - // PresetSchedulerService presetSchedulerService, - // SpotViewQuartileRepository spotViewQuartileRepository, - // @Qualifier("applicationTaskExecutor") Executor taskExecutor // ★ 여기 - // ) { - // this.khoaApiService = khoaApiService; - // this.openMeteoService = openMeteoService; - // this.presetSchedulerService = presetSchedulerService; - // this.spotViewQuartileRepository = spotViewQuartileRepository; - // this.taskExecutor = taskExecutor; - // } /** * 앞으로의 스케줄링 전략에 의해 수정될 부분입니다. * @author guwnoong diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b588ec20..ec958820 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,3 +3,16 @@ spring: name: MarineLeisure profiles: active: prod + +resilience4j.circuitbreaker: + instances: + openai-api: + sliding-window-size: 10 # 최근 10개의 요청을 기반으로 실패율 계산 + failure-rate-threshold: 50 # 실패율이 50% 이상이면 서킷을 OPEN + wait-duration-in-open-state: 10s # 서킷이 OPEN된 상태를 10초간 유지 + permitted-number-of-calls-in-half-open-state: 5 # HALF-OPEN 상태에서 5개의 테스트 요청을 허용 + kakao-api: + sliding-window-size: 10 # 최근 10개의 요청을 기반으로 실패율 계산 + failure-rate-threshold: 50 # 실패율이 50% 이상이면 서킷을 OPEN + wait-duration-in-open-state: 10s # 서킷이 OPEN된 상태를 10초간 유지 + permitted-number-of-calls-in-half-open-state: 5 # HALF-OPEN 상태에서 5개의 테스트 요청을 허용 \ No newline at end of file