diff --git a/BE-hubo-gillajabi-resources b/BE-hubo-gillajabi-resources index 5ea7ac9f..de8c9997 160000 --- a/BE-hubo-gillajabi-resources +++ b/BE-hubo-gillajabi-resources @@ -1 +1 @@ -Subproject commit 5ea7ac9f2a5cf3d4338f04a881980bacd72307a1 +Subproject commit de8c99978ddeb685984300e05310da3a78ed62f2 diff --git a/build.gradle b/build.gradle index 84881977..14dbd5ff 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' testImplementation 'org.springframework.boot:spring-boot-starter-test' + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.2.5' + + // https://mvnrepository.com/artifact/junit/junit testImplementation 'junit:junit:4.13.1' diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/presenation/CrawlController.java b/src/main/java/com/hubo/gillajabi/crawl/application/presenation/CrawlController.java deleted file mode 100644 index 0ade1adf..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/application/presenation/CrawlController.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.hubo.gillajabi.crawl.application.presenation; - - -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; -import com.hubo.gillajabi.crawl.application.service.CrawlFacadeService; -import com.hubo.gillajabi.crawl.domain.constant.CityName; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/crawl") -@RequiredArgsConstructor -@Tag(name = "crawl 컨트롤러", description = "관리자 공공데이터 포털 호출 api") -public class CrawlController { - - private final CrawlFacadeService crawlFacadeService; - - @Operation(summary = "전국 길 크롤링 ", description = "만약 해당 정보가 존재한다면 생략합니다.") - @GetMapping("/courses") - public ResponseEntity startCrawlingCurse(@Valid @RequestParam CityName cityName) { - - CrawlResponse.CourseResult response = crawlFacadeService.getCourse(cityName); - - return ResponseEntity.ok().body(response); - } - - @Operation(summary = "전국 길 테마 크롤링", description = "전국 길 테마 크롤링 (ex: 남파랑길)") - @GetMapping("/themes") - public ResponseEntity startCrawlingTheme(@Valid @RequestParam CityName cityName) { - - CrawlResponse.ThemeResult response = crawlFacadeService.getTheme(cityName); - - return ResponseEntity.ok().body(response); - } -} - - diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/presenation/RoadCrawlController.java b/src/main/java/com/hubo/gillajabi/crawl/application/presenation/RoadCrawlController.java new file mode 100644 index 00000000..11774a8b --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/application/presenation/RoadCrawlController.java @@ -0,0 +1,41 @@ +package com.hubo.gillajabi.crawl.application.presenation; + + +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; +import com.hubo.gillajabi.crawl.application.service.RoadCrawlFacadeService; +import com.hubo.gillajabi.crawl.domain.constant.CityCrawlName; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/road/crawl") +@RequiredArgsConstructor +@Tag(name = "Road crawl 컨트롤러", description = "관리자 공공데이터 포털 호출 api(길 관련)") +public class RoadCrawlController { + + private final RoadCrawlFacadeService roadCrawlFacadeService; + + @Operation(summary = "전국 길 크롤링 ", description = "만약 해당 정보가 존재한다면 생략합니다.") + @PostMapping("/courses") + public ResponseEntity startCrawlingCurse(@Valid @RequestParam CityCrawlName cityCrawlName) { + + RoadCrawlResponse.CourseResult response = roadCrawlFacadeService.getCourse(cityCrawlName); + + return ResponseEntity.ok().body(response); + } + + @Operation(summary = "전국 길 테마 크롤링", description = "전국 길 테마 크롤링 (ex: 남파랑길)") + @PostMapping("/themes") + public ResponseEntity startCrawlingTheme(@Valid @RequestParam CityCrawlName cityCrawlName) { + + RoadCrawlResponse.ThemeResult response = roadCrawlFacadeService.getTheme(cityCrawlName); + + return ResponseEntity.ok().body(response); + } +} + + diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/presenation/WeatherCrawlController.java b/src/main/java/com/hubo/gillajabi/crawl/application/presenation/WeatherCrawlController.java new file mode 100644 index 00000000..84565365 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/application/presenation/WeatherCrawlController.java @@ -0,0 +1,46 @@ +package com.hubo.gillajabi.crawl.application.presenation; + +import com.hubo.gillajabi.crawl.domain.service.WeatherCrawlService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/weather/crawl") +@RequiredArgsConstructor +@Tag(name = "Weather crawl 컨트롤러 ", description = "관리자 공공 데이터 포털 호출 api(날씨 관련)") +public class WeatherCrawlController { + + private final WeatherCrawlService weatherCrawlService; + + @Operation(summary = "전국 날씨 크롤링 (당일) ", description = "전국 날씨를 3일치를 들고 옵니다.") + @PostMapping("/current") + public ResponseEntity startWeatherCurrentCrawl() { + + weatherCrawlService.currentCrawl(); + + return ResponseEntity.ok().build(); + } + + @Operation(summary = "전국 날씨 크롤링 (일주일) ", description = "3일 이후 부터 ~7일까지의 날씨 정보를 크롤링합니다.") + @PostMapping("/medium-term") + public ResponseEntity startWeatherMediumTermCrawl() { + + weatherCrawlService.weatherMediumTermCrawl(); + + return ResponseEntity.ok().build(); + } + + @Operation(summary = "전국 기상 특보 크롤링 (지금 현재) ", description = "미구현") + @PostMapping("/weather_alert") + public ResponseEntity startWeatherAlertCrawl() { + + weatherCrawlService.alertCrawl(); + + return ResponseEntity.status(500).body("미구현"); + } + +} + diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/dto/response/CrawlResponse.java b/src/main/java/com/hubo/gillajabi/crawl/application/response/RoadCrawlResponse.java similarity index 94% rename from src/main/java/com/hubo/gillajabi/crawl/application/dto/response/CrawlResponse.java rename to src/main/java/com/hubo/gillajabi/crawl/application/response/RoadCrawlResponse.java index c2a02226..d652124f 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/application/dto/response/CrawlResponse.java +++ b/src/main/java/com/hubo/gillajabi/crawl/application/response/RoadCrawlResponse.java @@ -1,4 +1,4 @@ -package com.hubo.gillajabi.crawl.application.dto.response; +package com.hubo.gillajabi.crawl.application.response; import com.hubo.gillajabi.crawl.domain.entity.*; import io.swagger.v3.oas.annotations.media.Schema; @@ -11,7 +11,7 @@ @Getter @Setter -public class CrawlResponse { +public class RoadCrawlResponse { @Getter @Setter diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/service/BusanCourseHandler.java b/src/main/java/com/hubo/gillajabi/crawl/application/service/BusanCourseHandler.java deleted file mode 100644 index 2f369a1c..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/application/service/BusanCourseHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.hubo.gillajabi.crawl.application.service; - -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; -import org.springframework.stereotype.Service; - -@Service -public class BusanCourseHandler { - - public CrawlResponse.CourseResult handle() { - return null; - } -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/service/BusanThemeHandler.java b/src/main/java/com/hubo/gillajabi/crawl/application/service/BusanThemeHandler.java deleted file mode 100644 index 8ab81225..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/application/service/BusanThemeHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.hubo.gillajabi.crawl.application.service; - -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class BusanThemeHandler { - - public CrawlResponse.ThemeResult handle() { - return null; - } -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/service/CrawlFacadeService.java b/src/main/java/com/hubo/gillajabi/crawl/application/service/CrawlFacadeService.java deleted file mode 100644 index 344f6deb..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/application/service/CrawlFacadeService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.hubo.gillajabi.crawl.application.service; - - -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; -import com.hubo.gillajabi.crawl.domain.constant.CityName; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - - -@Service -@RequiredArgsConstructor -public class CrawlFacadeService { - - private final DuruCourseHandler duruCourseHandler; - private final BusanCourseHandler busanCourseHandler; - private final DuruThemeHandler duruThemeHandler; - private final BusanThemeHandler busanThemeHandler; - - public CrawlResponse.CourseResult getCourse(final CityName cityName) { - return switch (cityName) { - case DURU -> duruCourseHandler.handle(); - case BUSAN -> busanCourseHandler.handle(); - }; - } - - public CrawlResponse.ThemeResult getTheme(final CityName cityName) { - return switch (cityName) { - case DURU -> duruThemeHandler.handle(); - case BUSAN -> busanThemeHandler.handle(); - }; - } -} - - - - diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/service/DuruCourseHandler.java b/src/main/java/com/hubo/gillajabi/crawl/application/service/DuruCourseHandler.java deleted file mode 100644 index b013f0e7..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/application/service/DuruCourseHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.hubo.gillajabi.crawl.application.service; - -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; -import com.hubo.gillajabi.crawl.domain.entity.City; -import com.hubo.gillajabi.crawl.domain.entity.Course; -import com.hubo.gillajabi.crawl.domain.entity.CourseDetail; -import com.hubo.gillajabi.crawl.domain.entity.GpxInfo; -import com.hubo.gillajabi.crawl.domain.service.duru.*; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class DuruCourseHandler { - - private final CrawlDuruServiceImpl duruCrawlService; - private final CityDuruService cityService; - private final CourseDuruService courseDuruService; - private final CourseDetailDuruService courseDetailDuruService; - private final GpxInfoDuruService gpxInfoDuruService; - - public CrawlResponse.CourseResult handle() { - List rawCourses = duruCrawlService.crawlCourse(); - List cities = cityService.saveCity(rawCourses); - List courses = courseDuruService.saveDuruCourse(rawCourses, cities); - List courseDetails = courseDetailDuruService.saveDuruCourseDetail(rawCourses, courses); - List gpxInfos = gpxInfoDuruService.saveGpxInfo(courseDetails); - - return CrawlResponse.CourseResult.of(cities, courses, courseDetails, gpxInfos); - - } -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/service/DuruThemeHandler.java b/src/main/java/com/hubo/gillajabi/crawl/application/service/DuruThemeHandler.java deleted file mode 100644 index 542e909b..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/application/service/DuruThemeHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.hubo.gillajabi.crawl.application.service; - - -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; -import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.domain.service.duru.CourseDuruThemeService; -import com.hubo.gillajabi.crawl.domain.service.duru.CrawlDuruServiceImpl; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruThemeResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class DuruThemeHandler { - - private final CrawlDuruServiceImpl duruCrawlService; - private final CourseDuruThemeService courseDuruThemeService; - - public CrawlResponse.ThemeResult handle() { - List responseItems = duruCrawlService.crawlTheme(); - List themes = courseDuruThemeService.saveDuruTheme(responseItems); - return CrawlResponse.ThemeResult.from(themes); - } -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/application/service/RoadCrawlFacadeService.java b/src/main/java/com/hubo/gillajabi/crawl/application/service/RoadCrawlFacadeService.java new file mode 100644 index 00000000..361c79e9 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/application/service/RoadCrawlFacadeService.java @@ -0,0 +1,40 @@ +package com.hubo.gillajabi.crawl.application.service; + + +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; +import com.hubo.gillajabi.crawl.domain.constant.CityCrawlName; +import com.hubo.gillajabi.crawl.domain.service.busan.RoadBusanThemeHandler; +import com.hubo.gillajabi.crawl.domain.service.busan.RoadCrawlBusanCourseHandler; +import com.hubo.gillajabi.crawl.domain.service.duru.RoadCourseDuruHandler; +import com.hubo.gillajabi.crawl.domain.service.duru.RoadThemeDuruHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +public class RoadCrawlFacadeService { + + private final RoadCourseDuruHandler roadCourseDuruHandler; + private final RoadCrawlBusanCourseHandler roadCrawlBusanCourseHandler; + private final RoadThemeDuruHandler roadThemeDuruHandler; + private final RoadBusanThemeHandler roadBusanThemeHandler; + + public RoadCrawlResponse.CourseResult getCourse(final CityCrawlName cityCrawlName) { + return switch (cityCrawlName) { + case DURU -> roadCourseDuruHandler.handle(); + case BUSAN -> roadCrawlBusanCourseHandler.handle(); + }; + } + + public RoadCrawlResponse.ThemeResult getTheme(final CityCrawlName cityCrawlName) { + return switch (cityCrawlName) { + case DURU -> roadThemeDuruHandler.handle(); + case BUSAN -> roadBusanThemeHandler.handle(); + }; + } +} + + + + diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CityName.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CityCrawlName.java similarity index 78% rename from src/main/java/com/hubo/gillajabi/crawl/domain/constant/CityName.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/constant/CityCrawlName.java index 64f42955..b7c1a01d 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CityName.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CityCrawlName.java @@ -1,12 +1,12 @@ package com.hubo.gillajabi.crawl.domain.constant; -public enum CityName { +public enum CityCrawlName { DURU("두루누비"), BUSAN("부산"); private final String name; - CityName(String name) { + CityCrawlName(String name) { this.name = name; } diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CrawlType.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CrawlType.java deleted file mode 100644 index 7f33b296..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CrawlType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.hubo.gillajabi.crawl.domain.constant; - -public enum CrawlType { - COURSE, - THEME, -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CycleType.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CycleType.java index 47d6e3bb..96c7fd53 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CycleType.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/CycleType.java @@ -1,11 +1,11 @@ package com.hubo.gillajabi.crawl.domain.constant; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; public enum CycleType { CYCLE, SINGLE; - public static CycleType fromValue(DuruCourseResponse.Course course) { + public static CycleType fromValue(ApiCourseResponse.Course course) { return switch (course.getCrsCycle()) { case "순환형" -> CYCLE; case "비순환형" -> SINGLE; diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/ForecastType.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/ForecastType.java new file mode 100644 index 00000000..78b76f8e --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/ForecastType.java @@ -0,0 +1,7 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +public enum ForecastType { + CURRENT, // 당일 예보 + MEDIUM_TERM, // 중기 예보 + WEATHER_ALERT // 기상 특보 +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/MediumTermSkyCondition.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/MediumTermSkyCondition.java new file mode 100644 index 00000000..3959cec4 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/MediumTermSkyCondition.java @@ -0,0 +1,47 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +public enum MediumTermSkyCondition { + // 맑음 + CLEAR("맑음"), + + // 구름많음, 구름많고 비, 구름많고 눈, 구름많고 비/눈, 구름많고 소나기 + MOSTLY_CLOUDY("구름많음"), + MOSTLY_CLOUDY_WITH_RAIN("구름많고 비"), + MOSTLY_CLOUDY_WITH_SNOW("구름많고 눈"), + MOSTLY_CLOUDY_WITH_RAIN_AND_SNOW("구름많고 비/눈"), + MOSTLY_CLOUDY_WITH_SHOWERS("구름많고 소나기"), + + // 흐림, 흐리고 비, 흐리고 눈, 흐리고 비/눈, 흐리고 소나기 + CLOUDY("흐림"), + CLOUDY_WITH_RAIN("흐리고 비"), + CLOUDY_WITH_SNOW("흐리고 눈"), + CLOUDY_WITH_RAIN_AND_SNOW("흐리고 비/눈"), + CLOUDY_WITH_SHOWERS("흐리고 소나기"); + + private final String description; + + MediumTermSkyCondition(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public static MediumTermSkyCondition fromString(String value){ + return switch (value) { + case "맑음", "" -> CLEAR; + case "구름많음" -> MOSTLY_CLOUDY; + case "구름많고 비" -> MOSTLY_CLOUDY_WITH_RAIN; + case "구름많고 눈" -> MOSTLY_CLOUDY_WITH_SNOW; + case "구름많고 비/눈" -> MOSTLY_CLOUDY_WITH_RAIN_AND_SNOW; + case "구름많고 소나기" -> MOSTLY_CLOUDY_WITH_SHOWERS; + case "흐림" -> CLOUDY; + case "흐리고 비" -> CLOUDY_WITH_RAIN; + case "흐리고 눈" -> CLOUDY_WITH_SNOW; + case "흐리고 비/눈" -> CLOUDY_WITH_RAIN_AND_SNOW; + case "흐리고 소나기" -> CLOUDY_WITH_SHOWERS; + default -> throw new IllegalArgumentException("잘못된 하늘 상태 : " + value); + }; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/PrecipitationForm.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/PrecipitationForm.java new file mode 100644 index 00000000..53012263 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/PrecipitationForm.java @@ -0,0 +1,36 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +public enum PrecipitationForm { + NONE(0, "없음"), + RAIN(1, "비"), + RAIN_AND_SNOW(2, "비/눈"), + SNOW(3, "눈"), + SHOWER(4, "소나기"); + + private final int code; + private final String description; + + PrecipitationForm(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static PrecipitationForm fromCode(int code) { + return switch (code) { + case 0 -> NONE; + case 1 -> RAIN; + case 2 -> RAIN_AND_SNOW; + case 3 -> SNOW; + case 4 -> SHOWER; + default -> throw new IllegalArgumentException("잘못된 강수형태 " + code); + }; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/SkyCondition.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/SkyCondition.java new file mode 100644 index 00000000..5ffb5b26 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/SkyCondition.java @@ -0,0 +1,29 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +import lombok.Getter; + +@Getter +public enum SkyCondition { + CLEAR(1, "맑음"), + PARTLY_CLOUDY(2, "구름조금"), + MOSTLY_CLOUDY(3, "구름많음"), + CLOUDY(4, "흐림"); + + private final int code; + private final String description; + + SkyCondition(int code, String description) { + this.code = code; + this.description = description; + } + + public static SkyCondition fromCode(int code) { + return switch (code) { + case 1 -> CLEAR; + case 2 -> PARTLY_CLOUDY; + case 3 -> MOSTLY_CLOUDY; + case 4 -> CLOUDY; + default -> throw new IllegalArgumentException("잘못된 하늘 상태 코드 : " + code); + }; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/constant/WeatherRedisConstants.java b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/WeatherRedisConstants.java new file mode 100644 index 00000000..17dc1f78 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/constant/WeatherRedisConstants.java @@ -0,0 +1,28 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +import com.hubo.gillajabi.crawl.domain.entity.City; +import jakarta.annotation.Nullable; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class WeatherRedisConstants { + + private static final String WEATHER_API_RESPONSE = "weather:"; + private static final String WILD_CARD = "*"; + + /** + * redis key 생성 + * @param city + * @param date + * @param baseTime @Nullable + * @return key + */ + public static String makeWeatherKey(final City city, final LocalDate date, @Nullable final String baseTime) { + final String dateStr = date.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + if (baseTime == null) { + return WEATHER_API_RESPONSE + city.getName() + ":" + dateStr; + } + return WEATHER_API_RESPONSE + city.getName() + ":" + dateStr + ":" + baseTime; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/City.java b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/City.java index 432d17c9..ae2a9c29 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/City.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/City.java @@ -1,7 +1,7 @@ package com.hubo.gillajabi.crawl.domain.entity; import com.hubo.gillajabi.crawl.domain.constant.Province; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequestDTO; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; import jakarta.persistence.*; import lombok.*; @@ -26,7 +26,17 @@ public class City { @Column private String description; - public static City createCity(final CityRequestDTO cityRequestDTO) { - return new City(null, cityRequestDTO.getName(), cityRequestDTO.getProvince(), cityRequestDTO.getDescription()); + @Column + private Integer nx; + + @Column + private Integer ny; + + @Column(length = 8) + private String cityCode; + + public static City createCity(final CityRequest cityRequest) { + return new City(null, cityRequest.getName(), cityRequest.getProvince(), cityRequest.getDescription(),null + ,null,null); } } diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/Course.java b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/Course.java index 83fa17a7..21e02af0 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/Course.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/Course.java @@ -2,7 +2,7 @@ import com.hubo.gillajabi.crawl.domain.constant.CourseLevel; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequestDTO; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequest; import com.hubo.gillajabi.global.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -56,13 +56,13 @@ public class Course extends BaseEntity { private City city; - public static Course createCourse(final CourseRequestDTO request) { + public static Course createCourse(final CourseRequest request) { return new Course(null, request.getCourseName(), request.getDistance(), request.getTotalRequiredHours(), request.getLevel(), request.getShortDescription(), request.getCourseNumber(), null, null, null, request.getCourseTheme(), request.getCity()); } - public boolean checkUpdate(final CourseRequestDTO request) { + public boolean checkUpdate(final CourseRequest request) { boolean isUpdated = false; if (!this.originName.equals(request.getCourseName())) { @@ -100,7 +100,7 @@ public boolean checkUpdate(final CourseRequestDTO request) { return isUpdated; } - public void update(final CourseRequestDTO request) { + public void update(final CourseRequest request) { this.originName = request.getCourseName(); this.distance = request.getDistance(); this.totalTimeRequired = request.getTotalRequiredHours(); diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseDetail.java b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseDetail.java index f2720983..8b309c58 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseDetail.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseDetail.java @@ -1,7 +1,7 @@ package com.hubo.gillajabi.crawl.domain.entity; import com.hubo.gillajabi.crawl.domain.constant.CycleType; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequestDTO; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequest; import com.hubo.gillajabi.global.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -47,7 +47,7 @@ public class CourseDetail extends BaseEntity { @Enumerated(EnumType.STRING) private CycleType cycleType; // 순환 여부 - public static CourseDetail createCourseDetail(final CourseDetailRequestDTO request) { + public static CourseDetail createCourseDetail(final CourseDetailRequest request) { return new CourseDetail( null, request.getTourInfo(), @@ -62,7 +62,7 @@ public static CourseDetail createCourseDetail(final CourseDetailRequestDTO reque ); } - public void update(final CourseDetailRequestDTO request) { + public void update(final CourseDetailRequest request) { this.tourInfo = request.getTourInfo(); this.courseDescription = request.getCourseDescription(); this.startPoint = request.getStartPoint(); @@ -74,7 +74,7 @@ public void update(final CourseDetailRequestDTO request) { this.cycleType = request.getCycleType(); } - public boolean isCheckUpdate(final CourseDetailRequestDTO request) { + public boolean isCheckUpdate(final CourseDetailRequest request) { return !Objects.equals(this.tourInfo, request.getTourInfo()) || !Objects.equals(this.courseDescription, request.getCourseDescription()) || !Objects.equals(this.startPoint, request.getStartPoint()) diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseSection.java b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseSection.java index b1825c1c..0498f9af 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseSection.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseSection.java @@ -3,7 +3,6 @@ import com.hubo.gillajabi.crawl.domain.constant.CourseLevel; import com.hubo.gillajabi.global.BaseEntity; import jakarta.persistence.*; -import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseTheme.java b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseTheme.java index 36c6eb98..5c8b00b5 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseTheme.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/entity/CourseTheme.java @@ -1,10 +1,12 @@ package com.hubo.gillajabi.crawl.domain.entity; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequestDTO; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; import com.hubo.gillajabi.global.BaseEntity; import jakarta.persistence.*; import lombok.*; +import java.util.Objects; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -24,16 +26,16 @@ public class CourseTheme extends BaseEntity { @Column(columnDefinition = "TEXT") private String description; - public static CourseTheme createCourseTheme(CourseThemeRequestDTO requestDTO) { + public static CourseTheme createCourseTheme(CourseThemeRequest requestDTO) { return new CourseTheme(null, requestDTO.getName(), requestDTO.getShortDescription(), requestDTO.getDescription()); } - public void update(String themedescs, String linemsg) { - if (!themedescs.equals(this.description)) { - this.name = themedescs; + public void update(CourseThemeRequest requestDTO) { + if(!Objects.equals(this.getShortDescription(), requestDTO.getShortDescription())) { + this.shortDescription = requestDTO.getShortDescription(); } - if (!linemsg.equals(this.shortDescription)) { - this.shortDescription = linemsg; + if(!Objects.equals(this.description, requestDTO.getDescription())) { + this.description = requestDTO.getDescription(); } } } diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseService.java similarity index 87% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseService.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseService.java index 34cf9957..9018a710 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseService.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseService.java @@ -5,7 +5,7 @@ import java.net.URI; import java.util.Optional; -public interface ApiResponseService { +interface InternalApiResponseService { OptionalfindByRequestUrl(String string); String fetchApiResponse(URI uri); diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseServiceImpl.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseServiceImpl.java similarity index 93% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseServiceImpl.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseServiceImpl.java index 0414f1c0..748b3b8b 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseServiceImpl.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseServiceImpl.java @@ -15,18 +15,19 @@ @Transactional @RequiredArgsConstructor @Slf4j -public class ApiResponseServiceImpl implements ApiResponseService { +class InternalApiResponseServiceImpl implements InternalApiResponseService { private final CrawlApiResponseRepository crawlApiResponseRepository; private final RestTemplate restTemplate; - @Transactional(readOnly = true) @Override + @Transactional(readOnly = true) public Optional findByRequestUrl(String string) { return crawlApiResponseRepository.findByRequestUrl(string); } @Override + @Transactional(noRollbackFor = RuntimeException.class) public String fetchApiResponse(final URI uri) { String url = uri.toString(); Optional cachedResponse = getCachedResponse(url); diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/PrimaryCrawlingService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/PrimaryCrawlingService.java new file mode 100644 index 00000000..8ead58f3 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/PrimaryCrawlingService.java @@ -0,0 +1,90 @@ +package com.hubo.gillajabi.crawl.domain.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.AbstractApiResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ValidatableResponse; +import com.hubo.gillajabi.crawl.infrastructure.exception.CrawlException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.net.URI; + + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PrimaryCrawlingService { + + private final InternalApiResponseService responseCrawlService; + private final ObjectMapper objectMapper; + + @FunctionalInterface + public interface CrawlPageFunction { + AbstractApiResponse crawlPage(int pageNo) throws Exception; + } + + public List crawlItems(final CrawlPageFunction crawlPageFunction) { + int pageNo = 1; + List allItems = new ArrayList<>(); + while (true) { + try { + AbstractApiResponse response = crawlPageFunction.crawlPage(pageNo); + List items = extractItems(response); // 데이터 추출 + if (isLastPage(items)) { + return allItems; + } + allItems.addAll(items); + pageNo++; + } catch (Exception e) { + throw new CrawlException(500, "공공데이터 호출 중 예기치 못한 오류 발생 :" + e.getMessage()); + } + } + } + + public T crawlPage(Class responseType, URI uri) { + try { + final String response = responseCrawlService.fetchApiResponse(uri); + objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); + return objectMapper.readValue(response, responseType); + } catch (JsonProcessingException e) { + throw new CrawlException(500, "JSON 처리 중 오류 발생: " + e.getMessage()); + } + } + + public AbstractApiResponse crawlOnePage(Class itemType, URI uri) { + try { + final String response = responseCrawlService.fetchApiResponse(uri); + JavaType responseType = objectMapper.getTypeFactory().constructParametricType(AbstractApiResponse.class, itemType); + + return objectMapper.readValue(response, responseType); + } catch (JsonProcessingException e) { + throw new CrawlException(500, "JSON 처리 중 오류 발생: " + e.getMessage()); + } + } + + private boolean isLastPage(List items) { + return items == null || items.isEmpty(); + } + + public List extractItems(AbstractApiResponse response) { + if (response != null && response.getResponse() != null && response.getResponse().getBody() != null && response.getResponse().getBody().getItems() != null) { + return response.getResponse().getBody().getItems().getItem(); + } + return new ArrayList<>(); + } + + public String fetchApiResponse(URI uri) { + try { + return responseCrawlService.fetchApiResponse(uri); + } catch (Exception e) { + throw new CrawlException(500, "API 호출 중 예기치 못한 오류 발생: " + e.getMessage()); + } + } + + +} \ No newline at end of file diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlService.java new file mode 100644 index 00000000..a258f0b2 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlService.java @@ -0,0 +1,9 @@ +package com.hubo.gillajabi.crawl.domain.service; + +public interface WeatherCrawlService { + + void currentCrawl(); + void alertCrawl(); + void weatherMediumTermCrawl(); + +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlServiceImpl.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlServiceImpl.java new file mode 100644 index 00000000..4ed34ab6 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlServiceImpl.java @@ -0,0 +1,190 @@ +package com.hubo.gillajabi.crawl.domain.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hubo.gillajabi.crawl.domain.constant.ForecastType; +import com.hubo.gillajabi.crawl.domain.constant.WeatherRedisConstants; +import com.hubo.gillajabi.crawl.domain.entity.City; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.WeatherCurrentDto; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.WeatherTermDTO; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.AbstractApiResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiWeatherCurrentResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiWeatherMediumTermResponse; +import com.hubo.gillajabi.crawl.infrastructure.exception.CrawlException; +import com.hubo.gillajabi.crawl.infrastructure.persistence.CityRepository; +import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlApiBuilderHelper; +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import com.hubo.gillajabi.crawl.infrastructure.config.WeatherEndpointConfig; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.net.URI; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; + + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class WeatherCrawlServiceImpl implements WeatherCrawlService { + + private static Map weatherApiProperties; + + private final WeatherEndpointConfig weatherEndpointConfig; + private final CrawlApiBuilderHelper crawlApiBuilderHelper; + private final PrimaryCrawlingService primaryCrawlingService; + private final CityRepository cityRepository; + private final StringRedisTemplate stringRedisTemplate; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @PostConstruct + private void init() { + weatherApiProperties = weatherEndpointConfig.getEndPoint(); + validateWeatherApiProperties(); + } + + private void validateWeatherApiProperties() { + if (weatherApiProperties == null) { + throw new IllegalStateException("Weather Crawl Service 가 초기화 되지 않았습니다."); + } + } + + @Override + public void currentCrawl() { + List cities = cityRepository.findAll(); + cities.forEach(city -> { + try { + List currentWeatherData = crawlWeatherData(city, LocalDate.now()); + processWeatherData(city, currentWeatherData); + } catch (Exception e) { + log.info("날씨 데이터 수집 중 오류 발생: " + e.getMessage()); + } + }); + } + + private List crawlWeatherData(City city, LocalDate targetDate) { + return primaryCrawlingService.crawlItems(pageNo -> { + URI uri = crawlApiBuilderHelper.buildUri(weatherApiProperties.get(ForecastType.CURRENT), city.getNx(), city.getNy(), targetDate, pageNo); + return primaryCrawlingService.crawlPage(ApiWeatherCurrentResponse.class, uri); + }); + } + + private void processWeatherData(City city, List currentWeatherData) { + Map weatherDataByTime = new HashMap<>(); + Map dailyWeatherData = new HashMap<>(); + + for (ApiWeatherCurrentResponse.Current current : currentWeatherData) { + String dateTimeKey = current.getFcstDate() + current.getFcstTime(); + WeatherCurrentDto weatherCurrentDto = weatherDataByTime.computeIfAbsent(dateTimeKey, k -> new WeatherCurrentDto()); + weatherCurrentDto.updateWeatherDtoFromCurrent(current); + + WeatherCurrentDto dailyWeatherCurrentDto = dailyWeatherData.computeIfAbsent(current.getFcstDate(), k -> new WeatherCurrentDto()); + + updateTemperatureData(current, dailyWeatherCurrentDto); + } + + saveWeatherDataToRedis(city, weatherDataByTime, dailyWeatherData); + } + + /** + * 현재 날씨 데이터에서 최저/최고 기온 데이터를 추출하여 WeatherCurrentDto에 저장 + * 최저기온 : 오전 6시, 최고기온 : 오후 3시 + * @param current : response + * @param dailyWeatherCurrentDto : 날짜별 날씨 데이터 + */ + private void updateTemperatureData(ApiWeatherCurrentResponse.Current current, WeatherCurrentDto dailyWeatherCurrentDto) { + if ("TMN".equals(current.getCategory()) && "0600".equals(current.getFcstTime())) { + dailyWeatherCurrentDto.setLowTemperature(Float.parseFloat(current.getFcstValue())); + } + if ("TMX".equals(current.getCategory()) && "1500".equals(current.getFcstTime())) { + dailyWeatherCurrentDto.setHighTemperature(Float.parseFloat(current.getFcstValue())); + } + } + + private void saveWeatherDataToRedis(City city, Map weatherDataByTime, Map dailyWeatherData) { + weatherDataByTime.forEach((dateTimeKey, weatherCurrentDto) -> { + saveWeatherData(city, dateTimeKey, weatherCurrentDto); + }); + + dailyWeatherData.forEach((fcstDate, dailyWeatherCurrentDto) -> { + saveDailyWeatherData(city, fcstDate, dailyWeatherCurrentDto); + }); + } + + private void saveWeatherData(City city, String dateTimeKey, WeatherCurrentDto weatherCurrentDto) { + try { + String date = dateTimeKey.substring(0, 8); + String time = dateTimeKey.substring(8); + LocalDate parsedDate = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyyMMdd")); + String key = WeatherRedisConstants.makeWeatherKey(city, parsedDate, time); + String value = objectMapper.writeValueAsString(weatherCurrentDto); + stringRedisTemplate.opsForValue().set(key, value); + } catch (Exception e) { + throw new CrawlException(4, "날씨 데이터 저장 오류: " + e.getMessage()); + } + } + + private void saveDailyWeatherData(City city, String fcstDate, WeatherCurrentDto dailyWeatherCurrentDto) { + try { + LocalDate parsedDate = LocalDate.parse(fcstDate, DateTimeFormatter.ofPattern("yyyyMMdd")); + String dailyKey = WeatherRedisConstants.makeWeatherKey(city, parsedDate, null); + String dailyValue = objectMapper.writeValueAsString(dailyWeatherCurrentDto); + stringRedisTemplate.opsForValue().set(dailyKey, dailyValue); + } catch (Exception e) { + throw new CrawlException(4, "날씨 데이터 저장 오류: " + e.getMessage()); + } + } + + @Override + public void weatherMediumTermCrawl() { + List cities = cityRepository.findAll(); + cities.forEach(city -> { + LocalDate date = LocalDate.now(); + try { + ApiWeatherMediumTermResponse.Detail mediumTermWeatherDetailData = crawlMediumTermWeatherDetailData(date, city); + ApiWeatherMediumTermResponse.Temperature mediumTermWeatherTemperatureData = crawlMediumTermWeatherTemperatureData(date, city); + + processMediumTermWeatherData(city, date, mediumTermWeatherDetailData, mediumTermWeatherTemperatureData); + } catch (Exception e) { + log.info("중기 날씨 데이터 수집 중 오류 발생: " + e.getMessage()); + } + }); + } + + private ApiWeatherMediumTermResponse.Temperature crawlMediumTermWeatherTemperatureData(LocalDate date, City city) { + URI weatherTemperatureURI = crawlApiBuilderHelper.buildUri("getMidTa", weatherApiProperties.get(ForecastType.MEDIUM_TERM), city, 1, date); + AbstractApiResponse response = primaryCrawlingService.crawlOnePage(ApiWeatherMediumTermResponse.Temperature.class, weatherTemperatureURI); + return response.getResponse().getBody().getItems().getItem().get(0); + } + + private ApiWeatherMediumTermResponse.Detail crawlMediumTermWeatherDetailData(LocalDate date, City city) { + URI weatherDetailURI = crawlApiBuilderHelper.buildUri("getMidLandFcst", weatherApiProperties.get(ForecastType.MEDIUM_TERM), city, 1, date); + AbstractApiResponse response = primaryCrawlingService.crawlOnePage(ApiWeatherMediumTermResponse.Detail.class, weatherDetailURI); + return response.getResponse().getBody().getItems().getItem().get(0); + } + + private void processMediumTermWeatherData(City city, LocalDate date, ApiWeatherMediumTermResponse.Detail mediumTermWeatherDetailData, ApiWeatherMediumTermResponse.Temperature mediumTermWeatherTemperatureData) { + final LocalDate makeKeyDate = date.plusDays(3); + final WeatherTermDTO weatherTermDTO = WeatherTermDTO.of(mediumTermWeatherDetailData, mediumTermWeatherTemperatureData); + try { + String key = WeatherRedisConstants.makeWeatherKey(city, makeKeyDate, null); + String value = objectMapper.writeValueAsString(weatherTermDTO); + stringRedisTemplate.opsForValue().set(key, value); + + } catch (Exception e) { + throw new CrawlException(4, "중기 날씨 데이터 저장 오류: " + e.getMessage()); + } + } + + @Override + public void alertCrawl() { + //TODO: 예보 구현 필요 + } +} + diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadBusanThemeHandler.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadBusanThemeHandler.java new file mode 100644 index 00000000..f58123ed --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadBusanThemeHandler.java @@ -0,0 +1,14 @@ +package com.hubo.gillajabi.crawl.domain.service.busan; + +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RoadBusanThemeHandler { + + public RoadCrawlResponse.ThemeResult handle() { + return null; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadCrawlBusanCourseHandler.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadCrawlBusanCourseHandler.java new file mode 100644 index 00000000..a34eb1c6 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadCrawlBusanCourseHandler.java @@ -0,0 +1,12 @@ +package com.hubo.gillajabi.crawl.domain.service.busan; + +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; +import org.springframework.stereotype.Service; + +@Service +public class RoadCrawlBusanCourseHandler { + + public RoadCrawlResponse.CourseResult handle() { + return null; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/CrawlBusanServiceImpl.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadCrawlBusanServiceImpl.java similarity index 60% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/CrawlBusanServiceImpl.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadCrawlBusanServiceImpl.java index 25e44cbf..585cc926 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/CrawlBusanServiceImpl.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/busan/RoadCrawlBusanServiceImpl.java @@ -1,9 +1,8 @@ package com.hubo.gillajabi.crawl.domain.service.busan; ; -import com.hubo.gillajabi.crawl.domain.service.AbstarctCrawlService; -import com.hubo.gillajabi.crawl.infrastructure.config.RoadProperties; -import com.hubo.gillajabi.crawl.infrastructure.config.ApiProperties; -import com.hubo.gillajabi.crawl.domain.constant.CityName; +import com.hubo.gillajabi.crawl.infrastructure.config.RoadEndpointConfig; +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import com.hubo.gillajabi.crawl.domain.constant.CityCrawlName; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,27 +11,25 @@ @Service @RequiredArgsConstructor -public class CrawlBusanServiceImpl extends AbstarctCrawlService { +public class RoadCrawlBusanServiceImpl { - private final RoadProperties roadProperties; + private final RoadEndpointConfig roadEndpointConfig; private ApiProperties busanApiProperties; @PostConstruct private void init() { - this.busanApiProperties = roadProperties.getEndpoint(CityName.BUSAN); + this.busanApiProperties = roadEndpointConfig.getEndpoint(CityCrawlName.BUSAN); if (this.busanApiProperties == null) { throw new IllegalStateException("BUSAN Crawl Strategy 가 초기화 되지 않았습니다."); } } - @Override public List crawlCourse() { return List.of("Busan Course 1", "Busan Course 2", "Busan Course 3"); } - @Override public List crawlTheme(){ return List.of("Busan Theme 1", "Busan Theme 2", "Busan Theme 3"); } diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CityDuruService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CityDuruService.java deleted file mode 100644 index 208525e4..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CityDuruService.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.hubo.gillajabi.crawl.domain.service.duru; - -import com.hubo.gillajabi.crawl.domain.constant.Province; -import com.hubo.gillajabi.crawl.domain.entity.City; -import com.hubo.gillajabi.crawl.infrastructure.persistence.CityRepository; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; -import com.hubo.gillajabi.crawl.infrastructure.exception.CrawlException; -import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlResponseParserHelper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.List; - -@Service -@Transactional -@RequiredArgsConstructor -@Slf4j -public class CityDuruService { - - private final CityRepository cityRepository; - - public List saveCity(final List responseItems) { - List cities = new ArrayList<>(); - for (DuruCourseResponse.Course item : responseItems) { - processCourseItem(cities, item); - } - return cities; - } - - private void processCourseItem(final List cities, final DuruCourseResponse.Course item) { - try { - final CityRequestDTO cityRequestDTO = createCityRequestDTOFromCourseItem(item); - final City city = createOrFindCity(cityRequestDTO); - cities.add(city); - } catch (final CrawlException e) { - log.error("CityService.saveCity 실행중 문제 발생: " + e.getMessage()); - } - } - - private CityRequestDTO createCityRequestDTOFromCourseItem(final DuruCourseResponse.Course item) { - final String provinceName = CrawlResponseParserHelper.parseDuruResponseByProvince(item.getSigun()); - final Province province = Province.fromValue(provinceName); - final String cityName = CrawlResponseParserHelper.parseDuruResponseByCity(item.getSigun()); - - return CityRequestDTO.of(cityName, province); - } - - private City createOrFindCity(final CityRequestDTO cityRequestDTO) { - return cityRepository.findByNameAndProvince(cityRequestDTO.getName(), cityRequestDTO.getProvince()) - .orElseGet(() -> createAndSaveCity(cityRequestDTO)); - } - - private City createAndSaveCity(final CityRequestDTO cityRequestDTO) { - final City city = City.createCity(cityRequestDTO); - cityRepository.save(city); - return city; - } -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CrawlDuruServiceImpl.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CrawlDuruServiceImpl.java deleted file mode 100644 index ffa0ce69..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CrawlDuruServiceImpl.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.hubo.gillajabi.crawl.domain.service.duru; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.hubo.gillajabi.crawl.domain.constant.CityName; -import com.hubo.gillajabi.crawl.domain.service.AbstarctCrawlService; -import com.hubo.gillajabi.crawl.domain.service.ApiResponseService; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.AbstractDuruResponse; -import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlApiBuilderHelper; -import com.hubo.gillajabi.crawl.infrastructure.config.RoadProperties; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruThemeResponse; -import com.hubo.gillajabi.crawl.infrastructure.config.ApiProperties; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.ValidatableResponse; -import com.hubo.gillajabi.crawl.infrastructure.exception.CrawlException; -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.jsoup.Jsoup; -import org.springframework.stereotype.Service; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -@Service -@RequiredArgsConstructor -@Slf4j -public class CrawlDuruServiceImpl extends AbstarctCrawlService { - - private final RoadProperties roadProperties; - private final CrawlApiBuilderHelper crawlApiBuilderHelper; - private final ApiResponseService apiResponseService; - - private ApiProperties duruApiProperties; - - private static final ObjectMapper objectMapper = new ObjectMapper(); - - private static final int numOfRows = 100; - - @PostConstruct - private void init() { - this.duruApiProperties = roadProperties.getEndpoint(CityName.DURU); - validateDuruApiProperties(); - } - - private void validateDuruApiProperties() { - if (this.duruApiProperties == null) { - throw new IllegalStateException("DURU Crawl Strategy 가 초기화 되지 않았습니다."); - } - } - - @Override - public List crawlCourse() { - return crawlItems(this::crawlCoursePage); - } - - @Override - public List crawlTheme() { - List themes = crawlItems(this::crawlThemePage); - themes.forEach(theme -> theme.setThemedescs(Jsoup.parse(theme.getThemedescs()).text())); - return themes; - } - - private List crawlItems(final CrawlPageFunction crawlPageFunction){ - int pageNo = 1; - List allItems = new ArrayList<>(); - - while (true) { - try { - List items = crawlPageFunction.crawlPage(pageNo); - if (items.isEmpty()) { - break; - } - allItems.addAll(items); - pageNo++; - } catch (Exception e) { - throw new CrawlException(500, "공공데이터 DURU 호출 중 예기치 못한 오류 발생 :" + e.getMessage()); - } - } - return allItems; - } - - private List getItems(AbstractDuruResponse.Body body) { - if (body == null || body.getItems() == null || body.getItems().getItem() == null) { - return new ArrayList<>(); - } - return body.getItems().getItem(); - } - - private List crawlCoursePage(final int pageNo) throws JsonProcessingException { - DuruCourseResponse.Body body = - crawlPage(pageNo, "courseList", DuruCourseResponse.class).getResponse().getBody(); - return getItems(body); - } - - private List crawlThemePage(final int pageNo) throws JsonProcessingException { - DuruThemeResponse.Body body = - crawlPage(pageNo, "routeList", DuruThemeResponse.class).getResponse().getBody(); - return getItems(body); - } - - - private T crawlPage(final int pageNo, final String endpoint, Class responseType) throws JsonProcessingException { - final URI uri = buildUri(endpoint, pageNo); - final String response = apiResponseService.fetchApiResponse(uri); - objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); - final T parsedResponse = objectMapper.readValue(response, responseType); - crawlApiBuilderHelper.validateResponse(parsedResponse); - return parsedResponse; - } - - private URI buildUri(final String endpoint, final int pageNo) { - return crawlApiBuilderHelper.buildUri(endpoint, duruApiProperties, pageNo, numOfRows); - } - - @FunctionalInterface - private interface CrawlPageFunction { - List crawlPage(int pageNo) throws JsonProcessingException; - } -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCityDuruService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCityDuruService.java new file mode 100644 index 00000000..7dad21ad --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCityDuruService.java @@ -0,0 +1,62 @@ +package com.hubo.gillajabi.crawl.domain.service.duru; + +import com.hubo.gillajabi.crawl.domain.constant.Province; +import com.hubo.gillajabi.crawl.domain.entity.City; +import com.hubo.gillajabi.crawl.infrastructure.persistence.CityRepository; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.exception.CrawlException; +import com.hubo.gillajabi.crawl.infrastructure.util.helper.RoadCrawlResponseParserHelper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +@Slf4j +public class RoadCityDuruService { + + private final CityRepository cityRepository; + + public List saveCity(final List responseItems) { + List cities = new ArrayList<>(); + for (ApiCourseResponse.Course item : responseItems) { + processCourseItem(cities, item); + } + return cities; + } + + private void processCourseItem(final List cities, final ApiCourseResponse.Course item) { + try { + final CityRequest cityRequest = createCityRequestDTOFromCourseItem(item); + final City city = createOrFindCity(cityRequest); + cities.add(city); + } catch (final CrawlException e) { + log.error("CityService.saveCity 실행중 문제 발생: " + e.getMessage()); + } + } + + private CityRequest createCityRequestDTOFromCourseItem(final ApiCourseResponse.Course item) { + final String provinceName = RoadCrawlResponseParserHelper.parseDuruResponseByProvince(item.getSigun()); + final Province province = Province.fromValue(provinceName); + final String cityName = RoadCrawlResponseParserHelper.parseDuruResponseByCity(item.getSigun()); + + return CityRequest.of(cityName, province); + } + + private City createOrFindCity(final CityRequest cityRequest) { + return cityRepository.findByNameAndProvince(cityRequest.getName(), cityRequest.getProvince()) + .orElseGet(() -> createAndSaveCity(cityRequest)); + } + + private City createAndSaveCity(final CityRequest cityRequest) { + final City city = City.createCity(cityRequest); + cityRepository.save(city); + return city; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDetailDuruService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDetailDuruService.java similarity index 82% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDetailDuruService.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDetailDuruService.java index be8fdcbe..f7bee437 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDetailDuruService.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDetailDuruService.java @@ -2,9 +2,9 @@ import com.hubo.gillajabi.crawl.domain.entity.Course; import com.hubo.gillajabi.crawl.domain.entity.CourseDetail; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequestDTO; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequest; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseDetailRepository; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import com.hubo.gillajabi.crawl.infrastructure.exception.CrawlException; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseRepository; import lombok.RequiredArgsConstructor; @@ -19,7 +19,7 @@ @Service @Transactional @RequiredArgsConstructor -public class CourseDetailDuruService { +public class RoadCourseDetailDuruService { private final CourseDetailRepository courseDetailRepository; private final CourseRepository courseRepository; @@ -27,26 +27,26 @@ public class CourseDetailDuruService { private static final Pattern startPattern = Pattern.compile("(?:- )?시점(?:: | :| ) (.*?)(?:
|\\s)(?:교통편\\)|\uAD50\uD1B5\uD3B8\\)) (.*?)(?:
|\\s)"); private static final Pattern endPattern = Pattern.compile("(?:- )?종점(?:: | :| ) (.*?)(?:
|\\s)(?:교통편\\)|\uAD50\uD1B5\uD3B8\\)) (.*?)(?:
|\\s)"); - public List saveDuruCourseDetail(final List responseItems, final List courses) { + public List saveDuruCourseDetail(final List responseItems, final List courses) { List courseDetails = new ArrayList<>(); responseItems.forEach(item -> { final Course course = findCourseByName(courses, item.getCrsKorNm()); - final CourseDetailRequestDTO courseDetailRequestDTO = buildCourseDetailRequest(item); + final CourseDetailRequest courseDetailRequest = buildCourseDetailRequest(item); - final CourseDetail courseDetail = createOrUpdateDetail(courseDetailRequestDTO, course); + final CourseDetail courseDetail = createOrUpdateDetail(courseDetailRequest, course); updateCourseDetailByCourse(course, courseDetail); courseDetails.add(courseDetail); }); return courseDetails; } - private CourseDetailRequestDTO buildCourseDetailRequest(final DuruCourseResponse.Course item) { + private CourseDetailRequest buildCourseDetailRequest(final ApiCourseResponse.Course item) { final String startPoint = extractPoint(item.getTravelerinfo(), startPattern, 1); final String endPoint = extractPoint(item.getTravelerinfo(), endPattern, 1); final String startPointTransport = extractPoint(item.getTravelerinfo(), startPattern, 2); final String endPointTransport = extractPoint(item.getTravelerinfo(), endPattern, 2); - return CourseDetailRequestDTO.of(item, startPoint, endPoint, startPointTransport, endPointTransport); + return CourseDetailRequest.of(item, startPoint, endPoint, startPointTransport, endPointTransport); } private String extractPoint(final String info, final Pattern pattern, final int group) { @@ -54,12 +54,12 @@ private String extractPoint(final String info, final Pattern pattern, final int return matcher.find() ? matcher.group(group) : null; } - private CourseDetail createOrUpdateDetail(final CourseDetailRequestDTO request, final Course course) { + private CourseDetail createOrUpdateDetail(final CourseDetailRequest request, final Course course) { final CourseDetail existingDetail = course.getCourseDetail(); return existingDetail != null ? updateExistingDetail(existingDetail, request) : createNewCourseDetail(request, course); } - private CourseDetail updateExistingDetail(final CourseDetail detail, final CourseDetailRequestDTO request) { + private CourseDetail updateExistingDetail(final CourseDetail detail, final CourseDetailRequest request) { if (detail.isCheckUpdate(request)) { detail.update(request); return courseDetailRepository.save(detail); @@ -67,7 +67,7 @@ private CourseDetail updateExistingDetail(final CourseDetail detail, final Cours return detail; } - private CourseDetail createNewCourseDetail(final CourseDetailRequestDTO request, final Course course) { + private CourseDetail createNewCourseDetail(final CourseDetailRequest request, final Course course) { final CourseDetail newDetail = CourseDetail.createCourseDetail(request); course.addCourseDetail(newDetail); return courseDetailRepository.save(newDetail); diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruHandler.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruHandler.java new file mode 100644 index 00000000..169b78f7 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruHandler.java @@ -0,0 +1,34 @@ +package com.hubo.gillajabi.crawl.domain.service.duru; + +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; +import com.hubo.gillajabi.crawl.domain.entity.City; +import com.hubo.gillajabi.crawl.domain.entity.Course; +import com.hubo.gillajabi.crawl.domain.entity.CourseDetail; +import com.hubo.gillajabi.crawl.domain.entity.GpxInfo; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RoadCourseDuruHandler { + + private final RoadCrawlDuruServiceImpl duruCrawlService; + private final RoadCityDuruService cityService; + private final RoadCourseDuruService roadCourseDuruService; + private final RoadCourseDetailDuruService roadCourseDetailDuruService; + private final RoadGpxInfoDuruService roadGpxInfoDuruService; + + public RoadCrawlResponse.CourseResult handle() { + List rawCourses = duruCrawlService.crawlCourse(); + List cities = cityService.saveCity(rawCourses); + List courses = roadCourseDuruService.saveDuruCourse(rawCourses, cities); + List courseDetails = roadCourseDetailDuruService.saveDuruCourseDetail(rawCourses, courses); + List gpxInfos = roadGpxInfoDuruService.saveGpxInfo(courseDetails); + + return RoadCrawlResponse.CourseResult.of(cities, courses, courseDetails, gpxInfos); + + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruService.java similarity index 65% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruService.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruService.java index bdc99d01..55341477 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruService.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruService.java @@ -4,11 +4,11 @@ import com.hubo.gillajabi.crawl.domain.entity.City; import com.hubo.gillajabi.crawl.domain.entity.Course; import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequestDTO; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequest; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseThemeRepository; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseRepository; -import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlResponseParserHelper; +import com.hubo.gillajabi.crawl.infrastructure.util.helper.RoadCrawlResponseParserHelper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,30 +20,30 @@ @Service @RequiredArgsConstructor @Transactional -public class CourseDuruService { +public class RoadCourseDuruService { private final CourseRepository courseRepository; private final CourseThemeRepository courseThemeRepository; - public List saveDuruCourse(final List responseItems, final List cities) { + public List saveDuruCourse(final List responseItems, final List cities) { List courses = new ArrayList<>(); responseItems.forEach(item -> courses.add(processCourseItem(item, cities))); return courses; } - private Course processCourseItem(DuruCourseResponse.Course item, List cities) { + private Course processCourseItem(ApiCourseResponse.Course item, List cities) { City city = findCity(item, cities); CourseTheme courseTheme = findCourseTheme(item); - CourseRequestDTO courseRequestDTO = CourseRequestDTO.of(item, city, courseTheme); + CourseRequest courseRequest = CourseRequest.of(item, city, courseTheme); Optional existingCourse = courseRepository.findByOriginName(item.getCrsKorNm()); - return createOrUpdateCourse(courseRequestDTO, existingCourse); + return createOrUpdateCourse(courseRequest, existingCourse); } - private City findCity(final DuruCourseResponse.Course item, final List cities) { - final String provinceName = CrawlResponseParserHelper.parseDuruResponseByProvince(item.getSigun()); + private City findCity(final ApiCourseResponse.Course item, final List cities) { + final String provinceName = RoadCrawlResponseParserHelper.parseDuruResponseByProvince(item.getSigun()); final Province province = Province.fromValue(provinceName); - final String cityName = CrawlResponseParserHelper.parseDuruResponseByCity(item.getSigun()); + final String cityName = RoadCrawlResponseParserHelper.parseDuruResponseByCity(item.getSigun()); return cities.stream() .filter(c -> c.getName().equals(cityName) && c.getProvince().equals(province)) @@ -51,19 +51,19 @@ private City findCity(final DuruCourseResponse.Course item, final List cit .orElseThrow(() -> new IllegalArgumentException("City 정보가 없습니다.")); } - private CourseTheme findCourseTheme(final DuruCourseResponse.Course item) { + private CourseTheme findCourseTheme(final ApiCourseResponse.Course item) { final String courseName = item.getCrsKorNm().split(" ")[0]; return courseThemeRepository.findByName(courseName) .orElseThrow(() -> new IllegalArgumentException("CourseTheme 정보가 없습니다.")); } - private Course createOrUpdateCourse(final CourseRequestDTO request, final Optional existingCourse) { + private Course createOrUpdateCourse(final CourseRequest request, final Optional existingCourse) { return existingCourse .map(course -> updateExistingCourse(request, course)) .orElseGet(() -> createAndSaveNewCourse(request)); } - private Course updateExistingCourse(final CourseRequestDTO request, final Course existingCourse) { + private Course updateExistingCourse(final CourseRequest request, final Course existingCourse) { if (existingCourse.checkUpdate(request)) { existingCourse.update(request); courseRepository.save(existingCourse); @@ -71,7 +71,7 @@ private Course updateExistingCourse(final CourseRequestDTO request, final Course return existingCourse; } - private Course createAndSaveNewCourse(final CourseRequestDTO request) { + private Course createAndSaveNewCourse(final CourseRequest request) { Course newCourse = Course.createCourse(request); courseRepository.save(newCourse); return newCourse; diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruThemeService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruThemeService.java similarity index 64% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruThemeService.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruThemeService.java index 455728e4..97451f49 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruThemeService.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruThemeService.java @@ -1,8 +1,8 @@ package com.hubo.gillajabi.crawl.domain.service.duru; import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruThemeResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiThemeResponse; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseThemeRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,39 +14,40 @@ @Service @RequiredArgsConstructor @Transactional -public class CourseDuruThemeService { +public class RoadCourseDuruThemeService { private final CourseThemeRepository courseThemeRepository; - public List saveDuruTheme(List items) { + public List saveDuruTheme(List items) { List courseThemes = createOrUpdateCourseThemes(items); courseThemeRepository.saveAll(courseThemes); return courseThemes; } - private List createOrUpdateCourseThemes(List items) { + private List createOrUpdateCourseThemes(List items) { List courseThemes = new ArrayList<>(); - for (DuruThemeResponse.Theme item : items) { + for (ApiThemeResponse.Theme item : items) { CourseTheme courseTheme = findOrCreateCourseTheme(item); courseThemes.add(courseTheme); } return courseThemes; } - private CourseTheme findOrCreateCourseTheme(DuruThemeResponse.Theme item) { + private CourseTheme findOrCreateCourseTheme(ApiThemeResponse.Theme item) { return courseThemeRepository.findByName(item.getThemeNm()) .map(existingTheme -> updateExistingCourseTheme(existingTheme, item)) .orElseGet(() -> createNewCourseTheme(item)); } - private CourseTheme updateExistingCourseTheme(CourseTheme existingTheme, DuruThemeResponse.Theme item) { - existingTheme.update(item.getThemedescs(), item.getLinemsg()); + private CourseTheme updateExistingCourseTheme(CourseTheme existingTheme, ApiThemeResponse.Theme item) { + CourseThemeRequest requestDTO = CourseThemeRequest.from(item); + existingTheme.update(requestDTO); return existingTheme; } - private CourseTheme createNewCourseTheme(DuruThemeResponse.Theme item) { - CourseThemeRequestDTO requestDTO = CourseThemeRequestDTO.from(item); + private CourseTheme createNewCourseTheme(ApiThemeResponse.Theme item) { + CourseThemeRequest requestDTO = CourseThemeRequest.from(item); return CourseTheme.createCourseTheme(requestDTO); } } diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCrawlDuruServiceImpl.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCrawlDuruServiceImpl.java new file mode 100644 index 00000000..c06e2f73 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCrawlDuruServiceImpl.java @@ -0,0 +1,62 @@ +package com.hubo.gillajabi.crawl.domain.service.duru; + +import com.hubo.gillajabi.crawl.domain.constant.CityCrawlName; +import com.hubo.gillajabi.crawl.domain.service.PrimaryCrawlingService; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.AbstractApiResponse; +import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlApiBuilderHelper; +import com.hubo.gillajabi.crawl.infrastructure.config.RoadEndpointConfig; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiThemeResponse; +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RoadCrawlDuruServiceImpl extends RoadDuruAbstractCrawlDuruService { + + private final RoadEndpointConfig roadEndpointConfig; + private final CrawlApiBuilderHelper crawlApiBuilderHelper; + private final PrimaryCrawlingService primaryCrawlingService; + private static ApiProperties duruApiProperties; + + @PostConstruct + protected void init() { + duruApiProperties = roadEndpointConfig.getEndpoint(CityCrawlName.DURU); + validateDuruApiProperties(); + } + + private void validateDuruApiProperties() { + if (duruApiProperties == null) { + throw new IllegalStateException("DURU Crawl Service가 초기화 되지 않았습니다."); + } + } + + @Override + public List crawlCourse() { + return primaryCrawlingService.crawlItems(this::crawlCoursePage); + } + + @Override + public List crawlTheme() { + return primaryCrawlingService.crawlItems(this::crawlThemePage); + } + + private AbstractApiResponse crawlCoursePage(int pageNo) throws Exception { + URI uri = crawlApiBuilderHelper.buildUri("courseList", duruApiProperties, pageNo); + return primaryCrawlingService.crawlPage(ApiCourseResponse.class, uri); + } + + private AbstractApiResponse crawlThemePage(int pageNo) throws Exception { + URI uri = crawlApiBuilderHelper.buildUri("routeList", duruApiProperties, pageNo); + return primaryCrawlingService.crawlPage(ApiThemeResponse.class, uri); + } + + +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/AbstarctCrawlService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadDuruAbstractCrawlDuruService.java similarity index 52% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/AbstarctCrawlService.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadDuruAbstractCrawlDuruService.java index 08dcb94a..b78d0019 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/AbstarctCrawlService.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadDuruAbstractCrawlDuruService.java @@ -1,8 +1,8 @@ -package com.hubo.gillajabi.crawl.domain.service; +package com.hubo.gillajabi.crawl.domain.service.duru; import java.util.List; -public abstract class AbstarctCrawlService { +abstract class RoadDuruAbstractCrawlDuruService { public abstract List crawlCourse(); public abstract List crawlTheme(); diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/GpxInfoDuruService.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadGpxInfoDuruService.java similarity index 78% rename from src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/GpxInfoDuruService.java rename to src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadGpxInfoDuruService.java index c3f93af4..cc0462c3 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/GpxInfoDuruService.java +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadGpxInfoDuruService.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.hubo.gillajabi.crawl.domain.entity.CourseDetail; import com.hubo.gillajabi.crawl.domain.entity.GpxInfo; -import com.hubo.gillajabi.crawl.domain.service.ApiResponseService; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruGpxResponse; +import com.hubo.gillajabi.crawl.domain.service.PrimaryCrawlingService; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiDuruGpxResponse; import com.hubo.gillajabi.crawl.infrastructure.exception.CrawlException; import com.hubo.gillajabi.crawl.infrastructure.persistence.GpxInfoRepository; import lombok.RequiredArgsConstructor; @@ -23,10 +23,10 @@ @Transactional @RequiredArgsConstructor @Slf4j -public class GpxInfoDuruService { +public class RoadGpxInfoDuruService { private final GpxInfoRepository gpxInfoRepository; - private final ApiResponseService apiResponseService; + private final PrimaryCrawlingService primaryCrawlingService; private static final XmlMapper xmlMapper = new XmlMapper(); private static final ObjectMapper jsonMapper = new ObjectMapper(); @@ -52,7 +52,7 @@ private GpxInfo processCourseDetailAndSave(CourseDetail courseDetail) throws IOE return null; } - DuruGpxResponse gpxResponse = fetchGpxResponse(courseDetail.getGpxPath()); + ApiDuruGpxResponse gpxResponse = fetchGpxResponse(courseDetail.getGpxPath()); String jsonGpx = convertToJson(gpxResponse); return saveGpxInfo(jsonGpx, courseDetail); } @@ -61,13 +61,13 @@ private boolean shouldSkip(String path) { return path.endsWith(".kmz"); } - private DuruGpxResponse fetchGpxResponse(String gpxUrl) throws IOException { + private ApiDuruGpxResponse fetchGpxResponse(String gpxUrl) throws IOException { URI uri = URI.create(gpxUrl); - String response = apiResponseService.fetchApiResponse(uri); - return xmlMapper.readValue(response, DuruGpxResponse.class); + String response = primaryCrawlingService.fetchApiResponse(uri); + return xmlMapper.readValue(response, ApiDuruGpxResponse.class); } - private String convertToJson(DuruGpxResponse gpxResponse) throws JsonProcessingException { + private String convertToJson(ApiDuruGpxResponse gpxResponse) throws JsonProcessingException { return jsonMapper.writeValueAsString(gpxResponse); } diff --git a/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadThemeDuruHandler.java b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadThemeDuruHandler.java new file mode 100644 index 00000000..4e66a16a --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadThemeDuruHandler.java @@ -0,0 +1,24 @@ +package com.hubo.gillajabi.crawl.domain.service.duru; + + +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; +import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiThemeResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RoadThemeDuruHandler { + + private final RoadCrawlDuruServiceImpl duruCrawlService; + private final RoadCourseDuruThemeService roadCourseDuruThemeService; + + public RoadCrawlResponse.ThemeResult handle() { + List responseItems = duruCrawlService.crawlTheme(); + List themes = roadCourseDuruThemeService.saveDuruTheme(responseItems); + return RoadCrawlResponse.ThemeResult.from(themes); + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/RoadEndpointConfig.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/RoadEndpointConfig.java new file mode 100644 index 00000000..7d3d51ac --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/RoadEndpointConfig.java @@ -0,0 +1,23 @@ +package com.hubo.gillajabi.crawl.infrastructure.config; + +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import com.hubo.gillajabi.crawl.domain.constant.CityCrawlName; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.EnumMap; +import java.util.Map; + + +@Configuration +@ConfigurationProperties(prefix = "road") +@Getter +public class RoadEndpointConfig { + + private final Map roadEndpoints = new EnumMap<>(CityCrawlName.class); + + public ApiProperties getEndpoint(CityCrawlName cityCrawlName) { + return roadEndpoints.get(cityCrawlName); + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/RoadProperties.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/RoadProperties.java deleted file mode 100644 index 188a822f..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/RoadProperties.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.hubo.gillajabi.crawl.infrastructure.config; - -import com.hubo.gillajabi.crawl.domain.constant.CityName; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -import java.util.HashMap; -import java.util.Map; - - -@Configuration -@ConfigurationProperties(prefix = "road") -@Slf4j -@Getter -public class RoadProperties { - - private final Map roadEndpoints = new HashMap<>(); - - public ApiProperties getEndpoint(CityName cityName) { - return roadEndpoints.get(cityName); - } -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/WeatherEndpointConfig.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/WeatherEndpointConfig.java new file mode 100644 index 00000000..44040ff7 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/WeatherEndpointConfig.java @@ -0,0 +1,22 @@ +package com.hubo.gillajabi.crawl.infrastructure.config; + +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import com.hubo.gillajabi.crawl.domain.constant.ForecastType; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.EnumMap; +import java.util.Map; + +@Configuration +@ConfigurationProperties(prefix = "weather") +@Getter +public class WeatherEndpointConfig { + + private final Map weatherEndpoints = new EnumMap<>(ForecastType.class); + + public Map getEndPoint() { + return weatherEndpoints; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CityRequest.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CityRequest.java new file mode 100644 index 00000000..7a401377 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CityRequest.java @@ -0,0 +1,26 @@ +package com.hubo.gillajabi.crawl.infrastructure.dto.request; + +import com.hubo.gillajabi.crawl.domain.constant.Province; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class CityRequest { + + private String name; + + private Province province; + + private String description; + + public static CityRequest of(final String name, final Province province, final String description) { + return new CityRequest(name, province, description); + } + + public static CityRequest of(final String name, final Province province) { + return new CityRequest(name, province, null); + } +} + diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CityRequestDTO.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CityRequestDTO.java deleted file mode 100644 index 06bde805..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CityRequestDTO.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.hubo.gillajabi.crawl.infrastructure.dto.request; - -import com.hubo.gillajabi.crawl.domain.constant.Province; -import lombok.*; - -@Getter -@Setter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class CityRequestDTO { - - private String name; - - private Province province; - - private String description; - - public static CityRequestDTO of(final String name, final Province province, final String description) { - return new CityRequestDTO(name, province, description); - } - - public static CityRequestDTO of(final String name, final Province province) { - return new CityRequestDTO(name, province, null); - } -} - diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseDetailRequestDTO.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseDetailRequest.java similarity index 70% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseDetailRequestDTO.java rename to src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseDetailRequest.java index 6d392ebc..e4a424ee 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseDetailRequestDTO.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseDetailRequest.java @@ -1,14 +1,14 @@ package com.hubo.gillajabi.crawl.infrastructure.dto.request; import com.hubo.gillajabi.crawl.domain.constant.CycleType; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import lombok.*; @Getter @Setter @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class CourseDetailRequestDTO { +public class CourseDetailRequest { private String courseName; private String tourInfo; private String courseDescription; @@ -22,9 +22,9 @@ public class CourseDetailRequestDTO { private CycleType cycleType; - public static CourseDetailRequestDTO of(final DuruCourseResponse.Course item, final String startPoint, final String endPoint, - final String startPointTransport, final String endPointTransport) { - return new CourseDetailRequestDTO( + public static CourseDetailRequest of(final ApiCourseResponse.Course item, final String startPoint, final String endPoint, + final String startPointTransport, final String endPointTransport) { + return new CourseDetailRequest( item.getCrsKorNm(), item.getCrsTourInfo(), item.getCrsContents(), diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseRequestDTO.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseRequest.java similarity index 75% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseRequestDTO.java rename to src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseRequest.java index f9203f75..5f699bde 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseRequestDTO.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseRequest.java @@ -3,7 +3,7 @@ import com.hubo.gillajabi.crawl.domain.constant.CourseLevel; import com.hubo.gillajabi.crawl.domain.entity.City; import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import lombok.*; import org.jsoup.Jsoup; @@ -11,7 +11,7 @@ @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class CourseRequestDTO { +public class CourseRequest { private String courseName; private Integer distance; private Integer totalRequiredHours; @@ -21,11 +21,11 @@ public class CourseRequestDTO { private City city; private CourseTheme courseTheme; - public static CourseRequestDTO of(DuruCourseResponse.Course item, City city, CourseTheme courseTheme) { + public static CourseRequest of(ApiCourseResponse.Course item, City city, CourseTheme courseTheme) { CourseLevel level = CourseLevel.fromValue(item.getCrsLevel()); String shortDescription = Jsoup.parse(item.getCrsSummary()).text(); String courseNumber = parseCourseNumber(item.getCrsKorNm()); - return new CourseRequestDTO( + return new CourseRequest( item.getCrsKorNm(), Integer.parseInt(item.getCrsDstnc()), Integer.parseInt(item.getCrsTotlRqrmHour()), @@ -38,6 +38,10 @@ public static CourseRequestDTO of(DuruCourseResponse.Course item, City city, Cou } private static String parseCourseNumber(final String courseName) { + if (courseName == null || courseName.split(" ").length < 2) { + throw new IllegalArgumentException("잘못된 코스 이름 : " + courseName); + } return courseName.split(" ")[1].replace("코스", ""); } + } diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseThemeRequest.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseThemeRequest.java new file mode 100644 index 00000000..95ede7bd --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseThemeRequest.java @@ -0,0 +1,24 @@ +package com.hubo.gillajabi.crawl.infrastructure.dto.request; + +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiThemeResponse; +import lombok.*; +import org.jsoup.Jsoup; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class CourseThemeRequest { + + private String name; + private String shortDescription; + private String description; + + public static CourseThemeRequest from(ApiThemeResponse.Theme item) { + return new CourseThemeRequest(item.getThemeNm(), item.getLinemsg(), extractText(item.getThemedescs())); + } + + private static String extractText(String htmlContent) { + return Jsoup.parse(htmlContent).text(); + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseThemeRequestDTO.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseThemeRequestDTO.java deleted file mode 100644 index c973c0ab..00000000 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/CourseThemeRequestDTO.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.hubo.gillajabi.crawl.infrastructure.dto.request; - -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruThemeResponse; -import lombok.*; - -@Getter -@Setter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class CourseThemeRequestDTO { - - private String name; - private String shortDescription; - private String description; - - public static CourseThemeRequestDTO from(DuruThemeResponse.Theme item) { - return new CourseThemeRequestDTO(item.getThemeNm(), item.getLinemsg(), item.getThemedescs()); - } - -} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/WeatherCurrentDto.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/WeatherCurrentDto.java new file mode 100644 index 00000000..c91f431c --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/WeatherCurrentDto.java @@ -0,0 +1,108 @@ +package com.hubo.gillajabi.crawl.infrastructure.dto.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.hubo.gillajabi.crawl.domain.constant.PrecipitationForm; +import com.hubo.gillajabi.crawl.domain.constant.SkyCondition; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiWeatherCurrentResponse; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +@Setter +@Slf4j +public class WeatherCurrentDto { + + private Float liveTemperature; + + // 최저 기온 + private Float lowTemperature; + + // 최고 기온 + private Float highTemperature; + + // 날씨 + private SkyCondition skyCondition; + + // 기상 특보 TODO: 수정 (예 : 폭염 경보) + private String weatherAlert; + + // 습도 reh % 0~100 + private Integer humidity; + + // 강수형태 + private PrecipitationForm precipitationForm; + + // 강수확률 + private Integer precipitationProbability; + + // 강수량 mm + private Float precipitationAmount; + + // 적설량 + private Float snowfallAmount; + + // 풍속 m/s + private Float windSpeed; + + public void updateWeatherDtoFromCurrent( ApiWeatherCurrentResponse.Current current) { + try { + switch (current.getCategory()) { + case "POP": + this.setPrecipitationProbability(parseSafeInt(current.getFcstValue())); + break; + case "PTY": + this.setPrecipitationForm(PrecipitationForm.fromCode(parseSafeInt(current.getFcstValue()))); + break; + case "PCP": + this.setPrecipitationAmount(parseSafeFloat(current.getFcstValue(), "강수없음")); + break; + case "REH": + this.setHumidity(parseSafeInt(current.getFcstValue())); + break; + case "SNO": + this.setSnowfallAmount(parseSafeFloat(current.getFcstValue(), "적설없음")); + break; + case "SKY": + this.setSkyCondition(SkyCondition.fromCode(parseSafeInt(current.getFcstValue()))); + break; + case "TMP": + this.setLiveTemperature(Float.parseFloat(current.getFcstValue())); + break; + case "TMN": + this.setLowTemperature(Float.parseFloat(current.getFcstValue())); + break; + case "TMX": + this.setHighTemperature(Float.parseFloat(current.getFcstValue())); + break; + case "WSD": + this.setWindSpeed(Float.parseFloat(current.getFcstValue())); + break; + default: // 처리할 카테고리가 아닐 경우 + break; + } + } catch (NumberFormatException e) { + log.info("날씨 데이터 파싱 중 오류 발생: " + e.getMessage()); + } + } + + private float parseSafeFloat(String value, String defaultValue) { + try { + return Float.parseFloat(value); + } catch (NumberFormatException e) { + return defaultValue.equals("강수없음") || defaultValue.equals("적설없음") ? 0.0f : Float.parseFloat(defaultValue); + } + } + + private int parseSafeInt(String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return 0; + } + } + +} + + diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/WeatherTermDTO.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/WeatherTermDTO.java new file mode 100644 index 00000000..728350df --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/request/WeatherTermDTO.java @@ -0,0 +1,47 @@ +package com.hubo.gillajabi.crawl.infrastructure.dto.request; + +import com.hubo.gillajabi.crawl.domain.constant.MediumTermSkyCondition; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiWeatherMediumTermResponse; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class WeatherTermDTO { + + private int lowTemperature3; + private int highTemperature3; + private MediumTermSkyCondition skyCondition3; + private int lowTemperature4; + private int highTemperature4; + private MediumTermSkyCondition skyCondition4; + private int lowTemperature5; + private int highTemperature5; + private MediumTermSkyCondition skyCondition5; + private int lowTemperature6; + private int highTemperature6; + private MediumTermSkyCondition skyCondition6; + private int lowTemperature7; + private int highTemperature7; + private MediumTermSkyCondition skyCondition7; + + public static WeatherTermDTO of(ApiWeatherMediumTermResponse.Detail detail, ApiWeatherMediumTermResponse.Temperature temperature) { + WeatherTermDTO dto = new WeatherTermDTO(); + dto.setLowTemperature3(temperature.getTaMin3()); + dto.setHighTemperature3(temperature.getTaMax3()); + dto.setSkyCondition3(MediumTermSkyCondition.fromString(detail.getWf3Am())); + dto.setLowTemperature4(temperature.getTaMin4()); + dto.setHighTemperature4(temperature.getTaMax4()); + dto.setSkyCondition4(MediumTermSkyCondition.fromString(detail.getWf4Am())); + dto.setLowTemperature5(temperature.getTaMin5()); + dto.setHighTemperature5(temperature.getTaMax5()); + dto.setSkyCondition5(MediumTermSkyCondition.fromString(detail.getWf5Am())); + dto.setLowTemperature6(temperature.getTaMin6()); + dto.setHighTemperature6(temperature.getTaMax6()); + dto.setSkyCondition6(MediumTermSkyCondition.fromString(detail.getWf6Am())); + dto.setLowTemperature7(temperature.getTaMin7()); + dto.setHighTemperature7(temperature.getTaMax7()); + dto.setSkyCondition7(MediumTermSkyCondition.fromString(detail.getWf7Am())); + return dto; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/AbstractDuruResponse.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/AbstractApiResponse.java similarity index 85% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/AbstractDuruResponse.java rename to src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/AbstractApiResponse.java index 4b3b2468..a958a485 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/AbstractDuruResponse.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/AbstractApiResponse.java @@ -7,9 +7,14 @@ import java.util.List; +/** + * API 응답의 공통 구조를 정의한 추상 클래스 + * "공공 데이터 포털"에서 가져오는 api의 응답 구조를 정의한 추상 클래스 + * @param + */ @Getter @Setter -public class AbstractDuruResponse implements ValidatableResponse{ +public class AbstractApiResponse implements ValidatableResponse{ private Response response; diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruCourseResponse.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiCourseResponse.java similarity index 89% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruCourseResponse.java rename to src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiCourseResponse.java index f7464c9f..c9e2fa06 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruCourseResponse.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiCourseResponse.java @@ -5,7 +5,7 @@ @Getter @Setter -public class DuruCourseResponse extends AbstractDuruResponse { +public class ApiCourseResponse extends AbstractApiResponse { @Getter @Setter diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruGpxResponse.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiDuruGpxResponse.java similarity index 98% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruGpxResponse.java rename to src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiDuruGpxResponse.java index 5ebd9796..268677cc 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruGpxResponse.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiDuruGpxResponse.java @@ -14,7 +14,7 @@ @Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) // 알려지지 않은 속성 무시 -public class DuruGpxResponse { +public class ApiDuruGpxResponse { @JacksonXmlProperty(localName = "metadata") private Metadata metadata; diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruThemeResponse.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiThemeResponse.java similarity index 83% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruThemeResponse.java rename to src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiThemeResponse.java index 3b247328..624e2171 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/DuruThemeResponse.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiThemeResponse.java @@ -5,7 +5,7 @@ @Getter @Setter -public class DuruThemeResponse extends AbstractDuruResponse { +public class ApiThemeResponse extends AbstractApiResponse { @Getter @Setter diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherAlertResponse.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherAlertResponse.java new file mode 100644 index 00000000..9b6e06e1 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherAlertResponse.java @@ -0,0 +1,16 @@ +package com.hubo.gillajabi.crawl.infrastructure.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.Setter; + +public class ApiWeatherAlertResponse extends AbstractApiResponse { + + + @Getter + @Setter + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Alert { + //TODO : 예보 시스템 + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherCurrentResponse.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherCurrentResponse.java new file mode 100644 index 00000000..38b676cd --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherCurrentResponse.java @@ -0,0 +1,24 @@ +package com.hubo.gillajabi.crawl.infrastructure.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.Setter; + + +public class ApiWeatherCurrentResponse extends AbstractApiResponse { + + @Getter + @Setter + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Current { + private String baseDate; + private String baseTime; + private String category; + private String fcstDate; + private String fcstTime; + private String fcstValue; + private int nx; + private int ny; + } +} + diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherMediumTermResponse.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherMediumTermResponse.java new file mode 100644 index 00000000..4c963ab0 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/dto/response/ApiWeatherMediumTermResponse.java @@ -0,0 +1,72 @@ +package com.hubo.gillajabi.crawl.infrastructure.dto.response; + +import lombok.Getter; +import lombok.Setter; + +public class ApiWeatherMediumTermResponse { + + @Getter + @Setter + public static class Temperature { + private String regId; + private int taMin3; + private int taMin3Low; + private int taMin3High; + private int taMax3; + private int taMax3Low; + private int taMax3High; + private int taMin4; + private int taMin4Low; + private int taMin4High; + private int taMax4; + private int taMax4Low; + private int taMax4High; + private int taMin5; + private int taMin5Low; + private int taMin5High; + private int taMax5; + private int taMax5Low; + private int taMax5High; + private int taMin6; + private int taMin6Low; + private int taMin6High; + private int taMax6; + private int taMax6Low; + private int taMax6High; + private int taMin7; + private int taMin7Low; + private int taMin7High; + private int taMax7; + private int taMax7Low; + private int taMax7High; + } + + @Getter + @Setter + public static class Detail { + private String regId; + // 강수 확률 + private int rnSt3Am; + private int rnSt3Pm; + private int rnSt4Am; + private int rnSt4Pm; + private int rnSt5Am; + private int rnSt5Pm; + private int rnSt6Am; + private int rnSt6Pm; + private int rnSt7Am; + private int rnSt7Pm; + // 하늘 상태 + private String wf3Am; + private String wf3Pm; + private String wf4Am; + private String wf4Pm; + private String wf5Am; + private String wf5Pm; + private String wf6Am; + private String wf6Pm; + private String wf7Am; + private String wf7Pm; + } + +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/exception/CrawlExceptionCode.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/exception/CrawlExceptionCode.java index 9926e439..ae9cbb9a 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/exception/CrawlExceptionCode.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/exception/CrawlExceptionCode.java @@ -7,7 +7,9 @@ public enum CrawlExceptionCode { UNSIGNED_CALL_ERROR(33, "서명되지 않은 호출"), SERVICETIMEOUT_ERROR(5, "서비스 연결 실패 에러"), NODATA_ERROR(3, "데이터 없음 에러"), - DB_ERROR(2, "데이터베이스 에러"); + DB_ERROR(2, "데이터베이스 에러"), + //파싱 실패 에러 + PARSING_ERROR(4, "파싱 실패 에러"); private final int code; private final String message; diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/CrawlApiBuilderHelper.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/CrawlApiBuilderHelper.java index ab814d15..425b425f 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/CrawlApiBuilderHelper.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/CrawlApiBuilderHelper.java @@ -1,21 +1,27 @@ package com.hubo.gillajabi.crawl.infrastructure.util.helper; -import com.hubo.gillajabi.crawl.infrastructure.config.ApiProperties; +import com.hubo.gillajabi.crawl.domain.entity.City; +import com.hubo.gillajabi.global.common.dto.ApiProperties; import com.hubo.gillajabi.crawl.infrastructure.dto.response.ValidatableResponse; import org.springframework.stereotype.Component; import java.net.URI; import java.net.URISyntaxException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; @Component public class CrawlApiBuilderHelper { - public URI buildUri(final String endpointPath, final ApiProperties apiProperties, final int pageNo, final int numOfRows) { + private static final String numOfRows = "100"; + + + public URI buildUri(final String endpointPath, final ApiProperties apiProperties, final int pageNo) { try { final String url = apiProperties.getSiteUrl() .replace("{}", endpointPath) .replace("{serviceKey}", apiProperties.getEncoding()) - .replace("{numOfRows}", String.valueOf(numOfRows)) + .replace("{numOfRows}", numOfRows) .replace("{pageNo}", String.valueOf(pageNo)); return new URI(url); } catch (URISyntaxException e) { @@ -23,6 +29,62 @@ public URI buildUri(final String endpointPath, final ApiProperties apiProperties } } + + /** + * MEDIUM_TERM 날씨 API URI 생성 입니다. + * + * @param endPointPath : 어떤 API 를 호출할지 + * @param apiProperties : API 정보 + * @param pageNo : 페이지 번호 + * @param city : 도시 + * @param date : 검색 기준 날짜 + * @return URI + */ + public URI buildUri(final String endPointPath, final ApiProperties apiProperties, final City city, final int pageNo, final LocalDate date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + String formattedDate = date.format(formatter) + "0600"; + + try { + final String url = apiProperties.getSiteUrl() + .replace("{endPath}", endPointPath) + .replace("{serviceKey}", apiProperties.getEncoding()) + .replace("{numOfRows}", numOfRows) + .replace("{pageNo}", String.valueOf(pageNo)) + .replace("{cityCode}", city.getCityCode()) + .replace("{tmFc}", formattedDate); + return new URI(url); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("URI 생성 실패: " + e.getMessage(), e); + } + } + + /** + * current 날씨 API URI 생성 입니다. + * + * @param apiProperties + * @param pageNo : 페이지 번호 + * @param nx : x 좌표 + * @param ny : y 좌표 + * @param baseDate : 기준 날짜 + * @return + */ + public URI buildUri(final ApiProperties apiProperties, final int nx, final int ny, final LocalDate baseDate, final int pageNo) { + try { + String baseDateStr = baseDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + final String url = apiProperties.getSiteUrl() + .replace("{serviceKey}", apiProperties.getEncoding()) + .replace("{numOfRows}", numOfRows) + .replace("{pageNo}", String.valueOf(pageNo)) + .replace("{baseDate}", baseDateStr) + .replace("{nx}", String.valueOf(nx)) + .replace("{ny}", String.valueOf(ny)); + return new URI(url); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("URI 생성 실패: " + e.getMessage(), e); + } + } + + public void validateResponse(ValidatableResponse response) { response.validate(); } diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/CrawlResponseParserHelper.java b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/RoadCrawlResponseParserHelper.java similarity index 96% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/CrawlResponseParserHelper.java rename to src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/RoadCrawlResponseParserHelper.java index 650c1def..159914f4 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/CrawlResponseParserHelper.java +++ b/src/main/java/com/hubo/gillajabi/crawl/infrastructure/util/helper/RoadCrawlResponseParserHelper.java @@ -1,6 +1,6 @@ package com.hubo.gillajabi.crawl.infrastructure.util.helper; -public class CrawlResponseParserHelper { +public class RoadCrawlResponseParserHelper { /** diff --git a/src/main/java/com/hubo/gillajabi/global/common/aop/QueryCounterAop.java b/src/main/java/com/hubo/gillajabi/global/common/aop/QueryCounterAop.java index 7b42f238..7b641bf2 100644 --- a/src/main/java/com/hubo/gillajabi/global/common/aop/QueryCounterAop.java +++ b/src/main/java/com/hubo/gillajabi/global/common/aop/QueryCounterAop.java @@ -37,6 +37,13 @@ private LoggingFormat getCurrentLoggingForm() { return currentLoggingForm.get(); } + @Around("execution(* org.springframework.data.redis.core.RedisTemplate.*(..))") + public Object captureRedis(final ProceedingJoinPoint joinPoint) throws Throwable { + final Object result = joinPoint.proceed(); + getCurrentLoggingForm().redisQueryCountUp(); + return result; + } + @Around("within(@org.springframework.web.bind.annotation.RestController *)") public Object logExecutionTime(final ProceedingJoinPoint joinPoint) throws Throwable { final long startTime = System.currentTimeMillis(); diff --git a/src/main/java/com/hubo/gillajabi/global/common/config/RedisConfig.java b/src/main/java/com/hubo/gillajabi/global/common/config/RedisConfig.java new file mode 100644 index 00000000..890f72bc --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/global/common/config/RedisConfig.java @@ -0,0 +1,40 @@ +package com.hubo.gillajabi.global.common.config; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableRedisRepositories +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate(ObjectMapper objectMapper) { + final RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + + redisTemplate.setKeySerializer(new StringRedisSerializer()); + + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class); + redisTemplate.setValueSerializer(serializer); + + return redisTemplate; + } +} diff --git a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/ApiProperties.java b/src/main/java/com/hubo/gillajabi/global/common/dto/ApiProperties.java similarity index 78% rename from src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/ApiProperties.java rename to src/main/java/com/hubo/gillajabi/global/common/dto/ApiProperties.java index 96eb9818..a43afa44 100644 --- a/src/main/java/com/hubo/gillajabi/crawl/infrastructure/config/ApiProperties.java +++ b/src/main/java/com/hubo/gillajabi/global/common/dto/ApiProperties.java @@ -1,4 +1,4 @@ -package com.hubo.gillajabi.crawl.infrastructure.config; +package com.hubo.gillajabi.global.common.dto; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/hubo/gillajabi/global/common/dto/LoggingFormat.java b/src/main/java/com/hubo/gillajabi/global/common/dto/LoggingFormat.java index 0c6817bd..07ce4de3 100644 --- a/src/main/java/com/hubo/gillajabi/global/common/dto/LoggingFormat.java +++ b/src/main/java/com/hubo/gillajabi/global/common/dto/LoggingFormat.java @@ -16,6 +16,8 @@ public class LoggingFormat { private Long queryTime = 0L; + private Long redisQueryCounts = 0L; + private HttpStatus statusCode; private Long executionTime; @@ -36,6 +38,10 @@ public void addQueryTime(final Long queryTime) { this.queryTime += queryTime; } + public void redisQueryCountUp() { + redisQueryCounts++; + } + public void setStatusCode(final int statusCode) { this.statusCode = HttpStatus.valueOf(statusCode); } diff --git a/src/main/java/com/hubo/gillajabi/global/common/service/CrawlService.java b/src/main/java/com/hubo/gillajabi/global/common/service/CrawlService.java new file mode 100644 index 00000000..643ca459 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/global/common/service/CrawlService.java @@ -0,0 +1,12 @@ +package com.hubo.gillajabi.global.common.service; + +import com.hubo.gillajabi.crawl.domain.entity.CrawlApiResponse; + +import java.net.URI; +import java.util.Optional; + +public interface CrawlService { + Optional findByRequestUrl(String string); + + String fetchApiResponse(URI uri); +} diff --git a/src/main/java/com/hubo/gillajabi/global/common/service/CrawlServiceImpl.java b/src/main/java/com/hubo/gillajabi/global/common/service/CrawlServiceImpl.java new file mode 100644 index 00000000..f6809e83 --- /dev/null +++ b/src/main/java/com/hubo/gillajabi/global/common/service/CrawlServiceImpl.java @@ -0,0 +1,57 @@ +package com.hubo.gillajabi.global.common.service; + +import com.hubo.gillajabi.crawl.domain.entity.CrawlApiResponse; +import com.hubo.gillajabi.crawl.infrastructure.persistence.CrawlApiResponseRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +public class CrawlServiceImpl implements CrawlService{ + private final CrawlApiResponseRepository crawlApiResponseRepository; + private final RestTemplate restTemplate; + + @Transactional(readOnly = true) + @Override + public Optional findByRequestUrl(String url) { + return crawlApiResponseRepository.findByRequestUrl(url); + } + + @Override + public String fetchApiResponse(URI uri) { + String url = uri.toString(); + Optional cachedResponse = getCachedResponse(url); + return cachedResponse.orElseGet(() -> fetchFromApiAndCache(url, uri)); + } + + private Optional getCachedResponse(String url) { + return findByRequestUrl(url).map(CrawlApiResponse::getResponse); + } + + private String fetchFromApiAndCache(String url, URI uri) { + try { + String response = restTemplate.getForObject(uri, String.class); + log.info("API 응답을 가져왔습니다. url: {}", url); + saveApiResponse(url, response); + return response; + } catch (Exception e) { + throw new RuntimeException("API 응답을 가져오는 중 문제가 발생했습니다.", e); + } + } + + private void saveApiResponse(String apiUrl, String response) { + CrawlApiResponse crawlApiResponse = CrawlApiResponse.builder() + .requestUrl(apiUrl) + .response(response) + .build(); + crawlApiResponseRepository.save(crawlApiResponse); + } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/application/service/CrawlFacadeServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/application/service/CrawlFacadeServiceTest.java deleted file mode 100644 index dfa2cb6e..00000000 --- a/src/test/java/com/hubo/gillajabi/crawl/application/service/CrawlFacadeServiceTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.hubo.gillajabi.crawl.application.service; - -import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.*; - -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; -import com.hubo.gillajabi.crawl.domain.constant.CityName; -import com.navercorp.fixturemonkey.FixtureMonkey; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -@ExtendWith(SpringExtension.class) -public class CrawlFacadeServiceTest { - - @Mock - private DuruCourseHandler duruCourseHandler; - - @Mock - private BusanCourseHandler busanCourseHandler; - - @Mock - private DuruThemeHandler duruThemeHandler; - - @Mock - private BusanThemeHandler busanThemeHandler; - - @InjectMocks - private CrawlFacadeService crawlFacadeService; - - private final FixtureMonkey fixtureMonkey = FixtureMonkey.create(); - - @Test - @DisplayName("두루 코스를 제대로 호출") - public void testGetDuruCourse() { - // given - CrawlResponse.CourseResult duruCourseResult = fixtureMonkey.giveMeBuilder(CrawlResponse.CourseResult.class) - .sample(); - - when(duruCourseHandler.handle()).thenReturn(duruCourseResult); - - // when - assertEquals(duruCourseResult, crawlFacadeService.getCourse(CityName.DURU)); - - // then - verify(duruCourseHandler).handle(); - verifyNoMoreInteractions(duruThemeHandler, busanCourseHandler, busanThemeHandler); - } - - @Test - @DisplayName("부산 코스를 제대로 호출") - public void testGetBusanCourse() { - // given - CrawlResponse.CourseResult busanCourseResult = fixtureMonkey.giveMeBuilder(CrawlResponse.CourseResult.class) - .sample(); - - when(busanCourseHandler.handle()).thenReturn(busanCourseResult); - - // when - assertEquals(busanCourseResult, crawlFacadeService.getCourse(CityName.BUSAN)); - - // then - verify(busanCourseHandler).handle(); - verifyNoMoreInteractions(duruCourseHandler, duruThemeHandler, busanThemeHandler); - } - - @Test - @DisplayName("두루 테마를 제대로 호출") - public void testGetDuruTheme() { - // given - CrawlResponse.ThemeResult duruThemeResult = fixtureMonkey.giveMeBuilder(CrawlResponse.ThemeResult.class) - .sample(); - - when(duruThemeHandler.handle()).thenReturn(duruThemeResult); - - // when - assertEquals(duruThemeResult, crawlFacadeService.getTheme(CityName.DURU)); - - // then - verify(duruThemeHandler).handle(); - verifyNoMoreInteractions(duruCourseHandler, busanCourseHandler, busanThemeHandler); - } - - @Test - @DisplayName("부산 테마를 제대로 호출") - public void testGetBusanTheme() { - // given - CrawlResponse.ThemeResult busanThemeResult = fixtureMonkey.giveMeBuilder(CrawlResponse.ThemeResult.class) - .sample(); - - when(busanThemeHandler.handle()).thenReturn(busanThemeResult); - - // when - assertEquals(busanThemeResult, crawlFacadeService.getTheme(CityName.BUSAN)); - - // then - verify(busanThemeHandler).handle(); - verifyNoMoreInteractions(duruCourseHandler, duruThemeHandler, busanCourseHandler); - } -} diff --git a/src/test/java/com/hubo/gillajabi/crawl/application/service/RoadCrawlFacadeServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/application/service/RoadCrawlFacadeServiceTest.java new file mode 100644 index 00000000..e7cdbd92 --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/application/service/RoadCrawlFacadeServiceTest.java @@ -0,0 +1,107 @@ +package com.hubo.gillajabi.crawl.application.service; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; +import com.hubo.gillajabi.crawl.domain.constant.CityCrawlName; +import com.hubo.gillajabi.crawl.domain.service.busan.RoadBusanThemeHandler; +import com.hubo.gillajabi.crawl.domain.service.busan.RoadCrawlBusanCourseHandler; +import com.hubo.gillajabi.crawl.domain.service.duru.RoadCourseDuruHandler; +import com.hubo.gillajabi.crawl.domain.service.duru.RoadThemeDuruHandler; +import com.navercorp.fixturemonkey.FixtureMonkey; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +public class RoadCrawlFacadeServiceTest { + + @Mock + private RoadCourseDuruHandler roadCourseDuruHandler; + + @Mock + private RoadCrawlBusanCourseHandler roadCrawlBusanCourseHandler; + + @Mock + private RoadThemeDuruHandler roadThemeDuruHandler; + + @Mock + private RoadBusanThemeHandler roadBusanThemeHandler; + + @InjectMocks + private RoadCrawlFacadeService roadCrawlFacadeService; + + private final FixtureMonkey fixtureMonkey = FixtureMonkey.create(); + + @Test + @DisplayName("두루 코스를 제대로 호출") + public void testGetDuruCourse() { + // given + RoadCrawlResponse.CourseResult duruCourseResult = fixtureMonkey.giveMeBuilder(RoadCrawlResponse.CourseResult.class) + .sample(); + + when(roadCourseDuruHandler.handle()).thenReturn(duruCourseResult); + + // when + assertEquals(duruCourseResult, roadCrawlFacadeService.getCourse(CityCrawlName.DURU)); + + // then + verify(roadCourseDuruHandler).handle(); + verifyNoMoreInteractions(roadThemeDuruHandler, roadCrawlBusanCourseHandler, roadBusanThemeHandler); + } + + @Test + @DisplayName("부산 코스를 제대로 호출") + public void testGetBusanCourse() { + // given + RoadCrawlResponse.CourseResult busanCourseResult = fixtureMonkey.giveMeBuilder(RoadCrawlResponse.CourseResult.class) + .sample(); + + when(roadCrawlBusanCourseHandler.handle()).thenReturn(busanCourseResult); + + // when + assertEquals(busanCourseResult, roadCrawlFacadeService.getCourse(CityCrawlName.BUSAN)); + + // then + verify(roadCrawlBusanCourseHandler).handle(); + verifyNoMoreInteractions(roadCourseDuruHandler, roadThemeDuruHandler, roadBusanThemeHandler); + } + + @Test + @DisplayName("두루 테마를 제대로 호출") + public void testGetDuruTheme() { + // given + RoadCrawlResponse.ThemeResult duruThemeResult = fixtureMonkey.giveMeBuilder(RoadCrawlResponse.ThemeResult.class) + .sample(); + + when(roadThemeDuruHandler.handle()).thenReturn(duruThemeResult); + + // when + assertEquals(duruThemeResult, roadCrawlFacadeService.getTheme(CityCrawlName.DURU)); + + // then + verify(roadThemeDuruHandler).handle(); + verifyNoMoreInteractions(roadCourseDuruHandler, roadCrawlBusanCourseHandler, roadBusanThemeHandler); + } + + @Test + @DisplayName("부산 테마를 제대로 호출") + public void testGetBusanTheme() { + // given + RoadCrawlResponse.ThemeResult busanThemeResult = fixtureMonkey.giveMeBuilder(RoadCrawlResponse.ThemeResult.class) + .sample(); + + when(roadBusanThemeHandler.handle()).thenReturn(busanThemeResult); + + // when + assertEquals(busanThemeResult, roadCrawlFacadeService.getTheme(CityCrawlName.BUSAN)); + + // then + verify(roadBusanThemeHandler).handle(); + verifyNoMoreInteractions(roadCourseDuruHandler, roadThemeDuruHandler, roadCrawlBusanCourseHandler); + } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/constant/CycleTypeTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/CycleTypeTest.java index 1dfdc7e3..abeeb98d 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/constant/CycleTypeTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/CycleTypeTest.java @@ -1,6 +1,6 @@ package com.hubo.gillajabi.crawl.domain.constant; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,7 +12,7 @@ class CycleTypeTest { @Test @DisplayName("순환형은 CYCLE 반환") void 두루누비_순환형일경우_CYCLE반환(){ - DuruCourseResponse.Course course = new DuruCourseResponse.Course(); + ApiCourseResponse.Course course = new ApiCourseResponse.Course(); course.setCrsCycle("순환형"); assertEquals(CycleType.CYCLE, CycleType.fromValue(course)); @@ -21,7 +21,7 @@ class CycleTypeTest { @Test @DisplayName("비순환형이면 SINGLE 반환") void 두루누비_비순환형일경우_SINGLE반환(){ - DuruCourseResponse.Course course = new DuruCourseResponse.Course(); + ApiCourseResponse.Course course = new ApiCourseResponse.Course(); course.setCrsCycle("비순환형"); assertEquals(CycleType.SINGLE, CycleType.fromValue(course)); @@ -30,7 +30,7 @@ class CycleTypeTest { @Test @DisplayName("잘못된 순환형 값 예외 발생") void 두루누비_순환형이_잘못된_값일_경우(){ - DuruCourseResponse.Course course = new DuruCourseResponse.Course(); + ApiCourseResponse.Course course = new ApiCourseResponse.Course(); course.setCrsCycle("잘못된 값"); Exception exception = assertThrows(IllegalArgumentException.class, () -> CycleType.fromValue(course)); diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/constant/MediumTermSkyConditionTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/MediumTermSkyConditionTest.java new file mode 100644 index 00000000..3569ac90 --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/MediumTermSkyConditionTest.java @@ -0,0 +1,88 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MediumTermSkyConditionTest { + + @Test + @DisplayName("맑음은 CLEAR 반환") + void 맑음은_CLEAR반환() { + assertEquals(MediumTermSkyCondition.CLEAR, MediumTermSkyCondition.fromString("맑음")); + } + + @Test + @DisplayName("빈 문자열은 CLEAR 반환") + void 빈문자열은_CLEAR반환() { + assertEquals(MediumTermSkyCondition.CLEAR, MediumTermSkyCondition.fromString("")); + } + + @Test + @DisplayName("구름많음은 MOSTLY_CLOUDY 반환") + void 구름많음은_MOSTLY_CLOUDY반환() { + assertEquals(MediumTermSkyCondition.MOSTLY_CLOUDY, MediumTermSkyCondition.fromString("구름많음")); + } + + @Test + @DisplayName("구름많고 비는 MOSTLY_CLOUDY_WITH_RAIN 반환") + void 구름많고_비는_MOSTLY_CLOUDY_WITH_RAIN반환() { + assertEquals(MediumTermSkyCondition.MOSTLY_CLOUDY_WITH_RAIN, MediumTermSkyCondition.fromString("구름많고 비")); + } + + @Test + @DisplayName("구름많고 눈은 MOSTLY_CLOUDY_WITH_SNOW 반환") + void 구름많고_눈은_MOSTLY_CLOUDY_WITH_SNOW반환() { + assertEquals(MediumTermSkyCondition.MOSTLY_CLOUDY_WITH_SNOW, MediumTermSkyCondition.fromString("구름많고 눈")); + } + + @Test + @DisplayName("구름많고 비/눈은 MOSTLY_CLOUDY_WITH_RAIN_AND_SNOW 반환") + void 구름많고_비눈은_MOSTLY_CLOUDY_WITH_RAIN_AND_SNOW반환() { + assertEquals(MediumTermSkyCondition.MOSTLY_CLOUDY_WITH_RAIN_AND_SNOW, MediumTermSkyCondition.fromString("구름많고 비/눈")); + } + + @Test + @DisplayName("구름많고 소나기는 MOSTLY_CLOUDY_WITH_SHOWERS 반환") + void 구름많고_소나기는_MOSTLY_CLOUDY_WITH_SHOWERS반환() { + assertEquals(MediumTermSkyCondition.MOSTLY_CLOUDY_WITH_SHOWERS, MediumTermSkyCondition.fromString("구름많고 소나기")); + } + + @Test + @DisplayName("흐림은 CLOUDY 반환") + void 흐림은_CLOUDY반환() { + assertEquals(MediumTermSkyCondition.CLOUDY, MediumTermSkyCondition.fromString("흐림")); + } + + @Test + @DisplayName("흐리고 비는 CLOUDY_WITH_RAIN 반환") + void 흐리고_비는_CLOUDY_WITH_RAIN반환() { + assertEquals(MediumTermSkyCondition.CLOUDY_WITH_RAIN, MediumTermSkyCondition.fromString("흐리고 비")); + } + + @Test + @DisplayName("흐리고 눈은 CLOUDY_WITH_SNOW 반환") + void 흐리고_눈은_CLOUDY_WITH_SNOW반환() { + assertEquals(MediumTermSkyCondition.CLOUDY_WITH_SNOW, MediumTermSkyCondition.fromString("흐리고 눈")); + } + + @Test + @DisplayName("흐리고 비/눈은 CLOUDY_WITH_RAIN_AND_SNOW 반환") + void 흐리고_비눈은_CLOUDY_WITH_RAIN_AND_SNOW반환() { + assertEquals(MediumTermSkyCondition.CLOUDY_WITH_RAIN_AND_SNOW, MediumTermSkyCondition.fromString("흐리고 비/눈")); + } + + @Test + @DisplayName("흐리고 소나기는 CLOUDY_WITH_SHOWERS 반환") + void 흐리고_소나기는_CLOUDY_WITH_SHOWERS반환() { + assertEquals(MediumTermSkyCondition.CLOUDY_WITH_SHOWERS, MediumTermSkyCondition.fromString("흐리고 소나기")); + } + + @Test + @DisplayName("잘못된 값 예외 발생") + void 잘못된값_예외발생() { + assertThrows(IllegalArgumentException.class, () -> MediumTermSkyCondition.fromString("잘못된 값")); + } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/constant/PrecipitationFormTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/PrecipitationFormTest.java new file mode 100644 index 00000000..6cf4dc29 --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/PrecipitationFormTest.java @@ -0,0 +1,40 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class PrecipitationFormTest { + + @Test + @DisplayName("강수형태 코드가 0일 경우 없음을 반환") + void 강수형태_코드가_0일_경우_없음을_반환(){ + assertEquals(PrecipitationForm.NONE, PrecipitationForm.fromCode(0)); + } + + @Test + @DisplayName("강수형태 코드가 1일 경우 비를 반환") + void 강수형태_코드가_1일_경우_비를_반환(){ + assertEquals(PrecipitationForm.RAIN, PrecipitationForm.fromCode(1)); + } + + @Test + @DisplayName("강수형태 코드가 2일 경우 비/눈을 반환") + void 강수형태_코드가_2일_경우_비눈을_반환(){ + assertEquals(PrecipitationForm.RAIN_AND_SNOW, PrecipitationForm.fromCode(2)); + } + + @Test + @DisplayName("강수형태 코드가 3일 경우 눈을 반환") + void 강수형태_코드가_3일_경우_눈을_반환(){ + assertEquals(PrecipitationForm.SNOW, PrecipitationForm.fromCode(3)); + } + + @Test + @DisplayName("강수형태 코드가 4일 경우 소나기를 반환") + void 강수형태_코드가_4일_경우_소나기를_반환(){ + assertEquals(PrecipitationForm.SHOWER, PrecipitationForm.fromCode(4)); + } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/constant/SkyConditionTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/SkyConditionTest.java new file mode 100644 index 00000000..36e403ee --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/SkyConditionTest.java @@ -0,0 +1,43 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class SkyConditionTest { + + @Test + @DisplayName("하늘상태 코드 1은 CLEAR 반환") + void 하늘상태_코드_1일_경우() { + assertEquals(SkyCondition.CLEAR, SkyCondition.fromCode(1)); + } + + @Test + @DisplayName("하늘상태 코드 2은 PARTLY_CLOUDY 반환") + void 하늘상태_코드_2일_경우() { + assertEquals(SkyCondition.PARTLY_CLOUDY, SkyCondition.fromCode(2)); + } + + @Test + @DisplayName("하늘상태 코드 3은 MOSTLY_CLOUDY 반환") + void 하늘상태_코드_3일_경우() { + assertEquals(SkyCondition.MOSTLY_CLOUDY, SkyCondition.fromCode(3)); + } + + @Test + @DisplayName("하늘상태 코드 4은 CLOUDY 반환") + void 하늘상태_코드_4일_경우() { + assertEquals(SkyCondition.CLOUDY, SkyCondition.fromCode(4)); + } + + @Test + @DisplayName("잘못된 하늘상태 코드 예외 발생") + void 잘못된_하늘상태_코드일_경우() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> SkyCondition.fromCode(5)); + assertEquals("잘못된 하늘 상태 코드 : 5", exception.getMessage()); + } + + +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/constant/WeatherRedisConstantsTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/WeatherRedisConstantsTest.java new file mode 100644 index 00000000..8bb7579e --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/constant/WeatherRedisConstantsTest.java @@ -0,0 +1,63 @@ +package com.hubo.gillajabi.crawl.domain.constant; + +import com.hubo.gillajabi.crawl.domain.entity.City; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; + +class WeatherRedisConstantsTest { + + private City createCity(){ + CityRequest cityRequest = new CityRequest("Seoul", Province.SEOUL, "서울특별시"); + return City.createCity(cityRequest); + } + + @Test + @DisplayName("기본 키 생성") + public void 기본_키_생성() { + City city = createCity(); + LocalDate date = LocalDate.of(2023, 6, 28); + String result = WeatherRedisConstants.makeWeatherKey(city, date, null); + assertThat(result).isEqualTo("weather:Seoul:20230628"); + } + + @Test + @DisplayName("기본 키 생성 (기본 시간 포함)") + public void 기본_키_생성_기본_시간_포함() { + City city = createCity(); + LocalDate date = LocalDate.of(2023, 6, 28); + String result = WeatherRedisConstants.makeWeatherKey(city, date, "1200"); + assertThat(result).isEqualTo("weather:Seoul:20230628:1200"); + } + + @Test + @DisplayName("기본 키 생성 (도시 이름 테스트)") + public void 기본_키_생성_도시_이름_테스트() { + City city = createCity(); + LocalDate date = LocalDate.of(2023, 6, 28); + String result = WeatherRedisConstants.makeWeatherKey(city, date, null); + assertThat(result).isEqualTo("weather:Seoul:20230628"); + } + + @Test + @DisplayName("기본 키 생성 (날짜 테스트)") + public void 기본_키_생성_날짜_테스트() { + City city = createCity(); + LocalDate date = LocalDate.of(2023, 12, 31); + String result = WeatherRedisConstants.makeWeatherKey(city, date, null); + assertThat(result).isEqualTo("weather:Seoul:20231231"); + } + + @Test + @DisplayName("기본 키 생성 (시간 테스트)") + public void 기본_키_생성_시간_테스트() { + City city = createCity(); + LocalDate date = LocalDate.of(2023, 6, 28); + String result = WeatherRedisConstants.makeWeatherKey(city, date, "0600"); + assertThat(result).isEqualTo("weather:Seoul:20230628:0600"); + } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/entity/CityTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/entity/CityTest.java new file mode 100644 index 00000000..72f4da4c --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/entity/CityTest.java @@ -0,0 +1,26 @@ +package com.hubo.gillajabi.crawl.domain.entity; + +import com.hubo.gillajabi.crawl.domain.constant.Province; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CityTest { + + @Test + @DisplayName("createCity 메서드는 CityRequest를 사용하여 City 객체를 생성한다") + public void createCity_유효한_CityRequest로_City_객체_생성() { + // given + CityRequest cityRequest = CityRequest.of("Seoul", Province.SEOUL, "서울"); + + // when + City city = City.createCity(cityRequest); + + // then + assertEquals("Seoul", city.getName()); + assertEquals(Province.SEOUL, city.getProvince()); + assertEquals("서울", city.getDescription()); + } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/entity/CourseTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/entity/CourseTest.java new file mode 100644 index 00000000..3be80ca8 --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/entity/CourseTest.java @@ -0,0 +1,54 @@ +package com.hubo.gillajabi.crawl.domain.entity; + +import com.hubo.gillajabi.crawl.domain.constant.Province; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; +import com.navercorp.fixturemonkey.FixtureMonkey; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CourseTest { + + public static FixtureMonkey fixtureMonkey = FixtureMonkey.builder().build(); + + private ApiCourseResponse.Course createApiResponse() { + return fixtureMonkey.giveMeBuilder(ApiCourseResponse.Course.class) + .set("sigun", "경남 김해시") + .set("crsKorNm", "남파랑길 1코스") + .set("crsLevel", "1") + .set("crsDstnc", "5") + .set("crsTotlRqrmHour", "2") + .sample(); + } + + private City createCity() { + CityRequest cityRequest = CityRequest.of("김해시", Province.GYEONGNAM, "김해"); + return City.createCity(cityRequest); + } + + private static CourseTheme createCourseTheme() { + CourseThemeRequest courseThemeRequest = fixtureMonkey.giveMeOne(CourseThemeRequest.class); + return CourseTheme.createCourseTheme(courseThemeRequest); + } + + + @Test + @DisplayName("createCourse 메서드는 CourseReqeust를 사용한다") + public void createCourse_유효한_CourseRequest로_Course_객체_생성() { + // given + CourseRequest courseRequest = CourseRequest.of(createApiResponse(), createCity(), createCourseTheme()); + + // when + Course course = Course.createCourse(courseRequest); + + // then + assertEquals("남파랑길 1코스", course.getOriginName()); + assertEquals(5, course.getDistance()); + } + +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseServiceImplTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseServiceTest.java similarity index 90% rename from src/test/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseServiceImplTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseServiceTest.java index 77fa2b62..51b3fb9a 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/service/ApiResponseServiceImplTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/InternalApiResponseServiceTest.java @@ -19,7 +19,7 @@ import com.hubo.gillajabi.crawl.infrastructure.persistence.CrawlApiResponseRepository; @ExtendWith(MockitoExtension.class) -public class ApiResponseServiceImplTest { +class InternalApiResponseServiceTest { @Mock private CrawlApiResponseRepository crawlApiResponseRepository; @@ -28,7 +28,7 @@ public class ApiResponseServiceImplTest { private RestTemplate restTemplate; @InjectMocks - private ApiResponseServiceImpl apiResponseServiceImpl; + private InternalApiResponseServiceImpl roadCrawlApiResponseServiceImpl; @Test @DisplayName("URI를 호출하여 API 응답을 가져온다") @@ -42,7 +42,7 @@ public class ApiResponseServiceImplTest { when(restTemplate.getForObject(uri, String.class)).thenReturn(response); // When - String result = apiResponseServiceImpl.fetchApiResponse(uri); + String result = roadCrawlApiResponseServiceImpl.fetchApiResponse(uri); // Then assertEquals(response, result); @@ -68,7 +68,7 @@ public class ApiResponseServiceImplTest { )); // When - String result = apiResponseServiceImpl.fetchApiResponse(uri); + String result = roadCrawlApiResponseServiceImpl.fetchApiResponse(uri); // Then assertEquals(cachedResponse, result); @@ -87,7 +87,7 @@ public class ApiResponseServiceImplTest { // When / Then RuntimeException exception = assertThrows(RuntimeException.class, () -> { - apiResponseServiceImpl.fetchApiResponse(uri); + roadCrawlApiResponseServiceImpl.fetchApiResponse(uri); }); assertEquals("API 응답을 가져오는 중 문제가 발생했습니다.", exception.getMessage()); diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/PrimaryCrawlingServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/PrimaryCrawlingServiceTest.java new file mode 100644 index 00000000..090cee10 --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/PrimaryCrawlingServiceTest.java @@ -0,0 +1,7 @@ +package com.hubo.gillajabi.crawl.domain.service; + + + +class PrimaryCrawlingServiceTest +{ +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlServiceImplTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlServiceImplTest.java new file mode 100644 index 00000000..5cc7ec87 --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/WeatherCrawlServiceImplTest.java @@ -0,0 +1,4 @@ +package com.hubo.gillajabi.crawl.domain.service; + +public class WeatherCrawlServiceImplTest { +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CrawlDuruServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CrawlDuruServiceTest.java deleted file mode 100644 index 6415ffc5..00000000 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CrawlDuruServiceTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.hubo.gillajabi.crawl.domain.service.duru; - -import com.hubo.gillajabi.crawl.domain.service.ApiResponseService; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruThemeResponse; -import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlApiBuilderHelper; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class CrawlDuruServiceTest { - - @Mock - private ApiResponseService apiResponseService; - - @Mock - private CrawlApiBuilderHelper crawlApiBuilderHelper; - - @InjectMocks - private CrawlDuruServiceImpl crawlDuruService; - - private String readJsonFile(String path) throws IOException { - Path basePath = Paths.get("src", "test", "resources"); - Path fullPath = basePath.resolve(path); - return Files.readString(fullPath, StandardCharsets.UTF_8); - } - - @Test - @DisplayName("두루누비 코스 크롤링 테스트") - void testCrawlCourse() throws URISyntaxException, IOException { - // given - makeCrawlCourseMock(); - - // when - List courses = crawlDuruService.crawlCourse(); - - // then - assertEquals(1, courses.size()); - assertEquals("서해랑길 53코스", courses.get(0).getCrsKorNm()); - } - - private void makeCrawlCourseMock() throws URISyntaxException, IOException { - URI uriCoursePage1 = new URI("http://example.com/api/courses?page=1"); - URI uriCoursePage2 = new URI("http://example.com/api/courses?page=2"); - - when(crawlApiBuilderHelper.buildUri(eq("courseList"), any(), eq(1), eq(100))).thenReturn(uriCoursePage1); - when(crawlApiBuilderHelper.buildUri(eq("courseList"), any(), eq(2), eq(100))).thenReturn(uriCoursePage2); - - String courseResponsePage1 = readJsonFile("mockedResponses/courseDuruResponsePage1.json"); - String courseResponsePage2 = readJsonFile("mockedResponses/courseDuruResponsePage2.json"); - when(apiResponseService.fetchApiResponse(uriCoursePage1)).thenReturn(courseResponsePage1); - when(apiResponseService.fetchApiResponse(uriCoursePage2)).thenReturn(courseResponsePage2); - } - - @Test - @DisplayName("두루누비 테마 크롤링 테스트") - void testCrawlTheme() throws URISyntaxException, IOException { - // given - makeCrawlThemeMock(); - - // when - List themes = crawlDuruService.crawlTheme(); - - // then - assertFalse(themes.isEmpty()); - assertEquals(3, themes.size()); - } - - private void makeCrawlThemeMock() throws URISyntaxException, IOException { - URI uriThemePage1 = new URI("http://example.com/api/themes?page=1"); - URI uriThemePage2 = new URI("http://example.com/api/themes?page=2"); - - when(crawlApiBuilderHelper.buildUri(eq("routeList"), any(), eq(1), eq(100))).thenReturn(uriThemePage1); - when(crawlApiBuilderHelper.buildUri(eq("routeList"), any(), eq(2), eq(100))).thenReturn(uriThemePage2); - - String themeResponsePage1 = readJsonFile("mockedResponses/themeDuruResponsePage1.json"); - String themeResponsePage2 = readJsonFile("mockedResponses/themeDuruResponsePage2.json"); - when(apiResponseService.fetchApiResponse(uriThemePage1)).thenReturn(themeResponsePage1); - when(apiResponseService.fetchApiResponse(uriThemePage2)).thenReturn(themeResponsePage2); - } -} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CityDuruServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCityDuruServiceTest.java similarity index 77% rename from src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CityDuruServiceTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCityDuruServiceTest.java index eebfa373..9136c371 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CityDuruServiceTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCityDuruServiceTest.java @@ -2,16 +2,14 @@ import com.hubo.gillajabi.crawl.domain.constant.Province; import com.hubo.gillajabi.crawl.domain.entity.City; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import com.hubo.gillajabi.crawl.infrastructure.persistence.CityRepository; -import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlResponseParserHelper; import com.navercorp.fixturemonkey.FixtureMonkey; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; @@ -23,13 +21,13 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class CityDuruServiceTest { +class RoadCityDuruServiceTest { @Mock private CityRepository cityRepository; @InjectMocks - private CityDuruService cityDuruService; + private RoadCityDuruService roadCityDuruService; private final FixtureMonkey fixtureMonkey = FixtureMonkey.create(); @@ -42,14 +40,14 @@ public void findByNameAndProvince() { @Test void DuruCourseResponse_Course를_받아서_새로운_city객체를_생성하고_저장() { // given - List responseItems = fixtureMonkey.giveMeBuilder(DuruCourseResponse.Course.class) + List responseItems = fixtureMonkey.giveMeBuilder(ApiCourseResponse.Course.class) .set("sigun", "경남 김해시") .sampleList(1); findByNameAndProvince(); // when - List savedCities = cityDuruService.saveCity(responseItems); + List savedCities = roadCityDuruService.saveCity(responseItems); // then assertEquals(1, savedCities.size()); diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDetailDuruServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDetailDuruServiceTest.java similarity index 69% rename from src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDetailDuruServiceTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDetailDuruServiceTest.java index 159c9d7d..8f726c14 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDetailDuruServiceTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDetailDuruServiceTest.java @@ -6,10 +6,10 @@ import com.hubo.gillajabi.crawl.domain.entity.Course; import com.hubo.gillajabi.crawl.domain.entity.CourseDetail; import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseDetailRepository; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseRepository; import com.navercorp.fixturemonkey.FixtureMonkey; @@ -28,7 +28,7 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class CourseDetailDuruServiceTest { +class RoadCourseDetailDuruServiceTest { @Mock private CourseDetailRepository courseDetailRepository; @@ -37,12 +37,12 @@ class CourseDetailDuruServiceTest { private CourseRepository courseRepository; @InjectMocks - private CourseDetailDuruService courseDetailDuruService; + private RoadCourseDetailDuruService roadCourseDetailDuruService; private static final FixtureMonkey fixtureMonkey = FixtureMonkey.create(); - public DuruCourseResponse.Course giveMeDuruCourseResponse(){ - DuruCourseResponse.Course response = fixtureMonkey.giveMeBuilder(DuruCourseResponse.Course.class).sample(); + public ApiCourseResponse.Course giveMeDuruCourseResponse(){ + ApiCourseResponse.Course response = fixtureMonkey.giveMeBuilder(ApiCourseResponse.Course.class).sample(); response.setCrsKorNm("남해랑길 1코스"); response.setCrsLevel("1"); response.setGpxpath("http://example.com"); @@ -55,24 +55,24 @@ public DuruCourseResponse.Course giveMeDuruCourseResponse(){ } - public CityRequestDTO giveMeCityRequestDTO(){ - CityRequestDTO cityRequestDTO = fixtureMonkey.giveMeBuilder(CityRequestDTO.class).sample(); - cityRequestDTO.setProvince(Province.BUSAN); - return cityRequestDTO; + public CityRequest giveMeCityRequestDTO(){ + CityRequest cityRequest = fixtureMonkey.giveMeBuilder(CityRequest.class).sample(); + cityRequest.setProvince(Province.BUSAN); + return cityRequest; } - public CourseThemeRequestDTO giveMeCourseThemeRequestDTO(){ - CourseThemeRequestDTO courseThemeRequestDTO = fixtureMonkey.giveMeBuilder(CourseThemeRequestDTO.class).sample(); - courseThemeRequestDTO.setName("남파랑길"); - return courseThemeRequestDTO; + public CourseThemeRequest giveMeCourseThemeRequestDTO(){ + CourseThemeRequest courseThemeRequest = fixtureMonkey.giveMeBuilder(CourseThemeRequest.class).sample(); + courseThemeRequest.setName("남파랑길"); + return courseThemeRequest; } - public CourseRequestDTO giveMeCourseRequestDTO() { - DuruCourseResponse.Course response = giveMeDuruCourseResponse(); + public CourseRequest giveMeCourseRequestDTO() { + ApiCourseResponse.Course response = giveMeDuruCourseResponse(); City city = City.createCity(giveMeCityRequestDTO()); CourseTheme courseTheme = CourseTheme.createCourseTheme(giveMeCourseThemeRequestDTO()); - return CourseRequestDTO.of(response, city, courseTheme); + return CourseRequest.of(response, city, courseTheme); } @DisplayName("DuruCourseResponse.Course를 받아서 새로운 CourseDetail객체를 생성하고 저장") @@ -82,14 +82,14 @@ public CourseRequestDTO giveMeCourseRequestDTO() { List courses = new ArrayList<>(); courses.add(Course.createCourse(giveMeCourseRequestDTO())); - List responseItems = new ArrayList<>(); + List responseItems = new ArrayList<>(); responseItems.add(giveMeDuruCourseResponse()); when(courseRepository.save(any(Course.class))).thenAnswer(invocation -> invocation.getArgument(0)); when(courseDetailRepository.save(any(CourseDetail.class))).thenAnswer(invocation -> invocation.getArgument(0)); // when - List savedCourseDetails = courseDetailDuruService.saveDuruCourseDetail(responseItems, courses); + List savedCourseDetails = roadCourseDetailDuruService.saveDuruCourseDetail(responseItems, courses); // then assertEquals(1, savedCourseDetails.size()); diff --git a/src/test/java/com/hubo/gillajabi/crawl/application/service/DuruCourseHandlerTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruHandlerTest.java similarity index 53% rename from src/test/java/com/hubo/gillajabi/crawl/application/service/DuruCourseHandlerTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruHandlerTest.java index 54d7f510..e658f9f2 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/application/service/DuruCourseHandlerTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruHandlerTest.java @@ -1,15 +1,14 @@ -package com.hubo.gillajabi.crawl.application.service; +package com.hubo.gillajabi.crawl.domain.service.duru; -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; import com.hubo.gillajabi.crawl.domain.constant.CourseLevel; import com.hubo.gillajabi.crawl.domain.constant.Province; import com.hubo.gillajabi.crawl.domain.entity.*; -import com.hubo.gillajabi.crawl.domain.service.duru.*; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import com.navercorp.fixturemonkey.FixtureMonkey; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,30 +24,30 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class DuruCourseHandlerTest { +class RoadCourseDuruHandlerTest { @Mock - private CrawlDuruServiceImpl duruCrawlService; + private RoadCrawlDuruServiceImpl duruCrawlService; @Mock - private CityDuruService cityService; + private RoadCityDuruService cityService; @Mock - private CourseDuruService courseDuruService; + private RoadCourseDuruService roadCourseDuruService; @Mock - private CourseDetailDuruService courseDetailDuruService; + private RoadCourseDetailDuruService roadCourseDetailDuruService; @Mock - private GpxInfoDuruService gpxInfoDuruService; + private RoadGpxInfoDuruService roadGpxInfoDuruService; @InjectMocks - private DuruCourseHandler duruCourseHandler; + private RoadCourseDuruHandler roadCourseDuruHandler; private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder().build(); - public DuruCourseResponse.Course createDuruCourseResponse(){ - DuruCourseResponse.Course response = fixtureMonkey.giveMeBuilder(DuruCourseResponse.Course.class).sample(); + public ApiCourseResponse.Course createDuruCourseResponse(){ + ApiCourseResponse.Course response = fixtureMonkey.giveMeBuilder(ApiCourseResponse.Course.class).sample(); response.setCrsKorNm("남해랑길 1코스"); response.setCrsLevel("1"); response.setGpxpath("http://example.com"); @@ -59,30 +58,30 @@ public DuruCourseResponse.Course createDuruCourseResponse(){ return response; } private static CourseDetail createCourseDetail() { - CourseDetailRequestDTO courseDetailRequestDTO = fixtureMonkey.giveMeOne(CourseDetailRequestDTO.class); - return CourseDetail.createCourseDetail(courseDetailRequestDTO); + CourseDetailRequest courseDetailRequest = fixtureMonkey.giveMeOne(CourseDetailRequest.class); + return CourseDetail.createCourseDetail(courseDetailRequest); } - private static Course createCourse(DuruCourseResponse.Course mockCourseDuruResponse, City mockCity, CourseTheme mockCourseTheme) { - CourseRequestDTO courseRequestDTO = CourseRequestDTO.of(mockCourseDuruResponse, mockCity, mockCourseTheme); - courseRequestDTO.setCity(mockCity); - courseRequestDTO.setLevel(CourseLevel.fromValue(mockCourseDuruResponse.getCrsLevel())); + private static Course createCourse(ApiCourseResponse.Course mockCourseDuruResponse, City mockCity, CourseTheme mockCourseTheme) { + CourseRequest courseRequest = CourseRequest.of(mockCourseDuruResponse, mockCity, mockCourseTheme); + courseRequest.setCity(mockCity); + courseRequest.setLevel(CourseLevel.fromValue(mockCourseDuruResponse.getCrsLevel())); - return Course.createCourse(courseRequestDTO); + return Course.createCourse(courseRequest); } private static CourseTheme createCourseTheme() { - CourseThemeRequestDTO courseThemeRequestDTO = fixtureMonkey.giveMeOne(CourseThemeRequestDTO.class); - return CourseTheme.createCourseTheme(courseThemeRequestDTO); + CourseThemeRequest courseThemeRequest = fixtureMonkey.giveMeOne(CourseThemeRequest.class); + return CourseTheme.createCourseTheme(courseThemeRequest); } - private static City createCity(DuruCourseResponse.Course mockCourseDuruResponse) { - CityRequestDTO cityRequestDTO = CityRequestDTO.of(mockCourseDuruResponse.getCrsKorNm(), Province.GYEONGGI, "짧은소개글"); - return City.createCity(cityRequestDTO); + private static City createCity(ApiCourseResponse.Course mockCourseDuruResponse) { + CityRequest cityRequest = CityRequest.of(mockCourseDuruResponse.getCrsKorNm(), Province.GYEONGGI, "짧은소개글"); + return City.createCity(cityRequest); } private void mockDuruHandle() { - DuruCourseResponse.Course mockCourseDuruResponse = createDuruCourseResponse(); + ApiCourseResponse.Course mockCourseDuruResponse = createDuruCourseResponse(); City mockCity = createCity(mockCourseDuruResponse); CourseTheme mockCourseTheme = createCourseTheme(); Course mockCourse = createCourse(mockCourseDuruResponse, mockCity, mockCourseTheme); @@ -91,9 +90,9 @@ private void mockDuruHandle() { when(duruCrawlService.crawlCourse()).thenReturn(List.of(mockCourseDuruResponse)); when(cityService.saveCity(anyList())).thenReturn(List.of(mockCity)); - when(courseDuruService.saveDuruCourse(anyList(), anyList())).thenReturn(List.of(mockCourse)); - when(courseDetailDuruService.saveDuruCourseDetail(anyList(), anyList())).thenReturn(List.of(mockCourseDetail)); - when(gpxInfoDuruService.saveGpxInfo(anyList())).thenReturn(List.of(mockGpxInfo)); + when(roadCourseDuruService.saveDuruCourse(anyList(), anyList())).thenReturn(List.of(mockCourse)); + when(roadCourseDetailDuruService.saveDuruCourseDetail(anyList(), anyList())).thenReturn(List.of(mockCourseDetail)); + when(roadGpxInfoDuruService.saveGpxInfo(anyList())).thenReturn(List.of(mockGpxInfo)); } @@ -104,7 +103,7 @@ void testHandle() { mockDuruHandle(); // when - CrawlResponse.CourseResult result = duruCourseHandler.handle(); + RoadCrawlResponse.CourseResult result = roadCourseDuruHandler.handle(); // then assertEquals(1, result.getCityCount()); diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruServiceTest.java similarity index 71% rename from src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruServiceTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruServiceTest.java index 787febcb..c11f75f2 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseDuruServiceTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseDuruServiceTest.java @@ -4,10 +4,10 @@ import com.hubo.gillajabi.crawl.domain.entity.City; import com.hubo.gillajabi.crawl.domain.entity.Course; import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseRepository; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseThemeRepository; import com.navercorp.fixturemonkey.FixtureMonkey; @@ -27,7 +27,7 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class CourseDuruServiceTest { +class RoadCourseDuruServiceTest { @Mock private CourseRepository courseRepository; @@ -36,21 +36,21 @@ class CourseDuruServiceTest { private CourseThemeRepository courseThemeRepository; @InjectMocks - private CourseDuruService courseDuruService; + private RoadCourseDuruService roadCourseDuruService; private final static FixtureMonkey fixtureMonkey = FixtureMonkey.create(); public City createCity() { - CityRequestDTO cityRequestDTO = fixtureMonkey.giveMeBuilder(CityRequestDTO.class).sample(); - cityRequestDTO.setProvince(Province.GYEONGNAM); - cityRequestDTO.setName("김해시"); + CityRequest cityRequest = fixtureMonkey.giveMeBuilder(CityRequest.class).sample(); + cityRequest.setProvince(Province.GYEONGNAM); + cityRequest.setName("김해시"); - return City.createCity(cityRequestDTO); + return City.createCity(cityRequest); } - public DuruCourseResponse.Course createDuruCourseResponseCourse() { - DuruCourseResponse.Course response = fixtureMonkey.giveMeBuilder(DuruCourseResponse.Course.class).sample(); + public ApiCourseResponse.Course createDuruCourseResponseCourse() { + ApiCourseResponse.Course response = fixtureMonkey.giveMeBuilder(ApiCourseResponse.Course.class).sample(); response.setCrsKorNm("남해랑길 1코스"); response.setCrsLevel("1"); response.setGpxpath("http://example.com"); @@ -72,19 +72,19 @@ public void saveDuruCourse() { City city = createCity(); cities.add(city); - List responseItems = new ArrayList<>(); - DuruCourseResponse.Course response = createDuruCourseResponseCourse(); + List responseItems = new ArrayList<>(); + ApiCourseResponse.Course response = createDuruCourseResponseCourse(); responseItems.add(response); - CourseThemeRequestDTO courseThemeRequestDTO = fixtureMonkey.giveMeBuilder(CourseThemeRequestDTO.class).sample(); - CourseTheme courseTheme = CourseTheme.createCourseTheme(courseThemeRequestDTO); + CourseThemeRequest courseThemeRequest = fixtureMonkey.giveMeBuilder(CourseThemeRequest.class).sample(); + CourseTheme courseTheme = CourseTheme.createCourseTheme(courseThemeRequest); when(courseRepository.findByOriginName(any())).thenReturn(Optional.empty()); when(courseThemeRepository.findByName(any())).thenReturn(Optional.of(courseTheme)); - when(courseRepository.save(any())).thenReturn(Course.createCourse(CourseRequestDTO.of(response, city, courseTheme))); + when(courseRepository.save(any())).thenReturn(Course.createCourse(CourseRequest.of(response, city, courseTheme))); // when - List courses = courseDuruService.saveDuruCourse(responseItems, cities); + List courses = roadCourseDuruService.saveDuruCourse(responseItems, cities); // then assertEquals(1, courses.size()); diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseThemeServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseThemeDuruServiceTest.java similarity index 70% rename from src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseThemeServiceTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseThemeDuruServiceTest.java index 3414e798..def3cb26 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/CourseThemeServiceTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCourseThemeDuruServiceTest.java @@ -2,7 +2,7 @@ import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruThemeResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiThemeResponse; import com.hubo.gillajabi.crawl.infrastructure.persistence.CourseThemeRepository; import com.navercorp.fixturemonkey.FixtureMonkey; import org.junit.jupiter.api.DisplayName; @@ -18,13 +18,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith(MockitoExtension.class) -class CourseThemeServiceTest { +class RoadCourseThemeDuruServiceTest { @Mock private CourseThemeRepository courseThemeRepository; @InjectMocks - private CourseDuruThemeService courseDuruThemeService; + private RoadCourseDuruThemeService roadCourseDuruThemeService; private final FixtureMonkey fixtureMonkey = FixtureMonkey.create(); @@ -32,11 +32,11 @@ class CourseThemeServiceTest { @DisplayName("테마를 저장한다") public void 테마를_저장한다() { // given - List items = new ArrayList<>(); - items.add(fixtureMonkey.giveMeOne(DuruThemeResponse.Theme.class)); + List items = new ArrayList<>(); + items.add(fixtureMonkey.giveMeOne(ApiThemeResponse.Theme.class)); // when - List courseThemes = courseDuruThemeService.saveDuruTheme(items); + List courseThemes = roadCourseDuruThemeService.saveDuruTheme(items); // then assertEquals(1, courseThemes.size()); diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCrawlDuruServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCrawlDuruServiceTest.java new file mode 100644 index 00000000..2d5c6d6f --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadCrawlDuruServiceTest.java @@ -0,0 +1,91 @@ +package com.hubo.gillajabi.crawl.domain.service.duru; + +import com.hubo.gillajabi.crawl.domain.service.PrimaryCrawlingService; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiThemeResponse; +import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlApiBuilderHelper; +import com.hubo.gillajabi.crawl.infrastructure.config.RoadEndpointConfig; +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import com.navercorp.fixturemonkey.FixtureMonkey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class RoadCrawlDuruServiceTest { + + @Mock + private RoadEndpointConfig roadEndpointConfig; + + @Mock + private CrawlApiBuilderHelper crawlApiBuilderHelper; + + @Mock + private PrimaryCrawlingService primaryCrawlingService; + + @InjectMocks + private RoadCrawlDuruServiceImpl crawlDuruService; + + @BeforeEach + void setUp() { + ApiProperties duruApiProperties = new ApiProperties(); + duruApiProperties.setSiteUrl("http://example.com/api"); + when(roadEndpointConfig.getEndpoint(any())).thenReturn(duruApiProperties); + crawlDuruService.init(); + } + +// @Test +// @DisplayName("두루누비 코스 크롤링 테스트") +// void testCrawlCourse() throws URISyntaxException { +// // given +// makeCrawlCourseMock(); +// +// // when +// List courses = crawlDuruService.crawlCourse(); +// +// // then +// assertEquals(1, courses.size()); +// assertEquals("서해랑길 53코스", courses.get(0).getCrsKorNm()); +// } +// +// private void makeCrawlCourseMock() throws URISyntaxException { +// URI uriCoursePage1 = new URI("http://example.com/api/courseList?page=1"); +// +// when(crawlApiBuilderHelper.buildUri(eq("courseList"), any(), eq(1))).thenReturn(uriCoursePage1); +// +// ApiCourseResponse courseResponsePage1 = new ApiCourseResponse(); +// +// when(primaryCrawlingService.crawlPage(eq(ApiCourseResponse.class), eq(uriCoursePage1))).thenReturn(courseResponsePage1); +// } +// +// @Test +// @DisplayName("두루누비 테마 크롤링 테스트") +// void testCrawlTheme() throws URISyntaxException { +// // given +// makeCrawlThemeMock(); +// +// // when +// List themes = crawlDuruService.crawlTheme(); +// +// // then +// assertFalse(themes.isEmpty()); +// assertEquals(1, themes.size()); +// } +// +// private void makeCrawlThemeMock() throws URISyntaxException { +// URI uriThemePage1 = new URI("http://example.com/api/routeList?page=1"); +// +// when(crawlApiBuilderHelper.buildUri(eq("routeList"), any(), eq(1))).thenReturn(uriThemePage1); +// +// ApiThemeResponse themeResponsePage1 = FixtureMonkey.builder().build().giveMeOne(ApiThemeResponse.class); +// +// when(primaryCrawlingService.crawlPage(eq(ApiThemeResponse.class), eq(uriThemePage1))).thenReturn(themeResponsePage1); +// } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/GpxInfoDuruServiceTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadGpxInfoDuruServiceTest.java similarity index 60% rename from src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/GpxInfoDuruServiceTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadGpxInfoDuruServiceTest.java index 3b382b4f..8ee2b5df 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/GpxInfoDuruServiceTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadGpxInfoDuruServiceTest.java @@ -4,10 +4,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.hubo.gillajabi.crawl.domain.entity.*; -import com.hubo.gillajabi.crawl.domain.service.ApiResponseService; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruGpxResponse; +import com.hubo.gillajabi.crawl.domain.entity.CourseDetail; +import com.hubo.gillajabi.crawl.domain.entity.GpxInfo; +import com.hubo.gillajabi.crawl.domain.service.PrimaryCrawlingService; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseDetailRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiDuruGpxResponse; import com.hubo.gillajabi.crawl.infrastructure.persistence.GpxInfoRepository; import com.navercorp.fixturemonkey.FixtureMonkey; import org.junit.jupiter.api.BeforeEach; @@ -28,35 +29,35 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class GpxInfoDuruServiceTest { +class RoadGpxInfoDuruServiceTest { @Mock private GpxInfoRepository gpxInfoRepository; @Mock - private ApiResponseService apiResponseService; + private PrimaryCrawlingService responseCrawlService; @InjectMocks - private GpxInfoDuruService gpxInfoDuruService; + private RoadGpxInfoDuruService roadGpxInfoDuruService; private final FixtureMonkey fixtureMonkey = FixtureMonkey.create(); @BeforeEach public void setUp() { - CourseDetailRequestDTO courseDetailRequestDTO = fixtureMonkey.giveMeOne(CourseDetailRequestDTO.class); - courseDetailRequestDTO.setGpxPath("http://gpxpath.com.kmz"); + CourseDetailRequest courseDetailRequest = fixtureMonkey.giveMeOne(CourseDetailRequest.class); + courseDetailRequest.setGpxPath("http://gpxpath.com.kmz"); } public void mockApiResponseService() throws JsonProcessingException { // 외부 의존성을 모킹한다 - DuruGpxResponse duruGpxResponse = fixtureMonkey.giveMeOne(DuruGpxResponse.class); + ApiDuruGpxResponse apiDuruGpxResponse = fixtureMonkey.giveMeOne(ApiDuruGpxResponse.class); // XML로 변환 XmlMapper xmlMapper = new XmlMapper(); - String xmlResponse = xmlMapper.writeValueAsString(duruGpxResponse); + String xmlResponse = xmlMapper.writeValueAsString(apiDuruGpxResponse); // API 응답을 모킹 - when(apiResponseService.fetchApiResponse(any(URI.class))) + when(responseCrawlService.fetchApiResponse(any(URI.class))) .thenReturn(xmlResponse); } @@ -64,12 +65,12 @@ public void mockApiResponseService() throws JsonProcessingException { @DisplayName("List를 받아서 새로운 GpxInfo 객체를 생성하고 저장한다.") public void testSaveGpxInfoWithValidCourseDetails() throws JsonProcessingException { // given - List requestDTOs = new ArrayList<>(); + List requestDTOs = new ArrayList<>(); - CourseDetailRequestDTO courseDetailRequestDTO = fixtureMonkey.giveMeOne(CourseDetailRequestDTO.class); - courseDetailRequestDTO.setGpxPath("http://gpxpath.com"); + CourseDetailRequest courseDetailRequest = fixtureMonkey.giveMeOne(CourseDetailRequest.class); + courseDetailRequest.setGpxPath("http://gpxpath.com"); - requestDTOs.add(courseDetailRequestDTO); + requestDTOs.add(courseDetailRequest); List mockCourseDetails = requestDTOs.stream() .map(CourseDetail::createCourseDetail) @@ -79,7 +80,7 @@ public void testSaveGpxInfoWithValidCourseDetails() throws JsonProcessingExcepti mockApiResponseService(); // when - List savedGpxInfos = gpxInfoDuruService.saveGpxInfo(mockCourseDetails); + List savedGpxInfos = roadGpxInfoDuruService.saveGpxInfo(mockCourseDetails); // then assertEquals(mockCourseDetails.size(), savedGpxInfos.size()); @@ -89,19 +90,19 @@ public void testSaveGpxInfoWithValidCourseDetails() throws JsonProcessingExcepti @DisplayName("gpxPath가 kmz로 끝나는 경우 패스한다") public void gpxPath가_kmz로_끝나는_경우() { //given - List requestDTOs = new ArrayList<>(); + List requestDTOs = new ArrayList<>(); - CourseDetailRequestDTO courseDetailRequestDTO = fixtureMonkey.giveMeOne(CourseDetailRequestDTO.class); - courseDetailRequestDTO.setGpxPath("http://gpxpath.com.kmz"); + CourseDetailRequest courseDetailRequest = fixtureMonkey.giveMeOne(CourseDetailRequest.class); + courseDetailRequest.setGpxPath("http://gpxpath.com.kmz"); - requestDTOs.add(courseDetailRequestDTO); + requestDTOs.add(courseDetailRequest); List mockCourseDetails = requestDTOs.stream() .map(CourseDetail::createCourseDetail) .toList(); //when - List savedGpxInfos = gpxInfoDuruService.saveGpxInfo(mockCourseDetails); + List savedGpxInfos = roadGpxInfoDuruService.saveGpxInfo(mockCourseDetails); //then assertEquals(0, savedGpxInfos.size()); diff --git a/src/test/java/com/hubo/gillajabi/crawl/application/service/DuruThemeHandlerTest.java b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadThemeDuruHandlerTest.java similarity index 56% rename from src/test/java/com/hubo/gillajabi/crawl/application/service/DuruThemeHandlerTest.java rename to src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadThemeDuruHandlerTest.java index ec33b1dc..e4f3d3d2 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/application/service/DuruThemeHandlerTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/domain/service/duru/RoadThemeDuruHandlerTest.java @@ -1,11 +1,9 @@ -package com.hubo.gillajabi.crawl.application.service; +package com.hubo.gillajabi.crawl.domain.service.duru; -import com.hubo.gillajabi.crawl.application.dto.response.CrawlResponse; +import com.hubo.gillajabi.crawl.application.response.RoadCrawlResponse; import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; -import com.hubo.gillajabi.crawl.domain.service.duru.CourseDuruThemeService; -import com.hubo.gillajabi.crawl.domain.service.duru.CrawlDuruServiceImpl; -import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequestDTO; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.DuruThemeResponse; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiThemeResponse; import com.navercorp.fixturemonkey.FixtureMonkey; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,16 +19,16 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class DuruThemeHandlerTest { +class RoadThemeDuruHandlerTest { @Mock - private CrawlDuruServiceImpl duruCrawlService; + private RoadCrawlDuruServiceImpl duruCrawlService; @Mock - private CourseDuruThemeService courseDuruThemeService; + private RoadCourseDuruThemeService roadCourseDuruThemeService; @InjectMocks - private DuruThemeHandler duruThemeHandler; + private RoadThemeDuruHandler roadThemeDuruHandler; @Test @DisplayName("두루누비 테마를 제대로 호출") @@ -39,23 +37,23 @@ void testHandle() { FixtureMonkey fixtureMonkey = FixtureMonkey.create(); // Mock 생성 - List mockResponseItems = fixtureMonkey.giveMe(DuruThemeResponse.Theme.class, 5); + List mockResponseItems = fixtureMonkey.giveMe(ApiThemeResponse.Theme.class, 5); // CourseTheme 생성 List mockThemes = mockResponseItems.stream() .map(theme -> CourseTheme.createCourseTheme( - new CourseThemeRequestDTO(theme.getThemeNm(), theme.getLinemsg(), theme.getThemedescs()))) + new CourseThemeRequest(theme.getThemeNm(), theme.getLinemsg(), theme.getThemedescs()))) .collect(Collectors.toList()); when(duruCrawlService.crawlTheme()).thenReturn(mockResponseItems); - when(courseDuruThemeService.saveDuruTheme(anyList())).thenReturn(mockThemes); + when(roadCourseDuruThemeService.saveDuruTheme(anyList())).thenReturn(mockThemes); // when - CrawlResponse.ThemeResult result = duruThemeHandler.handle(); + RoadCrawlResponse.ThemeResult result = roadThemeDuruHandler.handle(); // then assertEquals(mockThemes.size(), result.getThemeCount()); verify(duruCrawlService).crawlTheme(); - verify(courseDuruThemeService).saveDuruTheme(mockResponseItems); + verify(roadCourseDuruThemeService).saveDuruTheme(mockResponseItems); } } diff --git a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/RoadPropertiesTest.java b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/RoadEndpointConfigTest.java similarity index 73% rename from src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/RoadPropertiesTest.java rename to src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/RoadEndpointConfigTest.java index 6e6e23c3..f9dd4454 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/RoadPropertiesTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/RoadEndpointConfigTest.java @@ -1,8 +1,8 @@ package com.hubo.gillajabi.crawl.infraStructure.config; -import com.hubo.gillajabi.crawl.domain.constant.CityName; -import com.hubo.gillajabi.crawl.infrastructure.config.ApiProperties; -import com.hubo.gillajabi.crawl.infrastructure.config.RoadProperties; +import com.hubo.gillajabi.crawl.domain.constant.CityCrawlName; +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import com.hubo.gillajabi.crawl.infrastructure.config.RoadEndpointConfig; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -13,22 +13,22 @@ @SpringBootTest @ActiveProfiles("test") -class RoadPropertiesTest { +class RoadEndpointConfigTest { @Autowired - private RoadProperties roadProperties; + private RoadEndpointConfig roadEndpointConfig; @Test @DisplayName("road-yml에 정의된 road를 올바르게 읽어온다.") void testRoadProperties() { - ApiProperties duruProperties = roadProperties.getEndpoint(CityName.DURU); + ApiProperties duruProperties = roadEndpointConfig.getEndpoint(CityCrawlName.DURU); assertThat(duruProperties).isNotNull(); assertThat(duruProperties.getEndpoint()).isEqualTo("http://example.com"); assertThat(duruProperties.getEncoding()).isEqualTo("serviceKey"); assertThat(duruProperties.getDecoding()).isEqualTo("serviceKey"); assertThat(duruProperties.getSiteUrl()).isEqualTo("http://example.com"); - ApiProperties busanProperties = roadProperties.getEndpoint(CityName.BUSAN); + ApiProperties busanProperties = roadEndpointConfig.getEndpoint(CityCrawlName.BUSAN); assertThat(busanProperties).isNotNull(); assertThat(busanProperties.getEndpoint()).isEqualTo("http://example.com"); assertThat(busanProperties.getEncoding()).isEqualTo("serviceKey"); diff --git a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/WeatherEndpointConfigTest.java b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/WeatherEndpointConfigTest.java new file mode 100644 index 00000000..f15b5d54 --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/config/WeatherEndpointConfigTest.java @@ -0,0 +1,54 @@ +package com.hubo.gillajabi.crawl.infraStructure.config; + +import com.hubo.gillajabi.crawl.domain.constant.ForecastType; +import com.hubo.gillajabi.crawl.infrastructure.config.WeatherEndpointConfig; +import com.hubo.gillajabi.global.common.dto.ApiProperties; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest +@EnableConfigurationProperties(WeatherEndpointConfig.class) +@ActiveProfiles("test") +class WeatherEndpointConfigTest { + + @Autowired + private WeatherEndpointConfig weatherEndpointConfig; + + @Test + public void testCurrentWeatherEndpointConfig() { + ApiProperties currentProperties = weatherEndpointConfig.getEndPoint().get(ForecastType.CURRENT); + assertNotNull(currentProperties); + assertEquals("http://example.com", currentProperties.getEndpoint()); + assertEquals("serviceKey", currentProperties.getEncoding()); + assertEquals("serviceKey", currentProperties.getDecoding()); + assertEquals("http://example.com", currentProperties.getSiteUrl()); + } + + @Test + public void testWeatherAlertEndpointConfig() { + ApiProperties alertProperties = weatherEndpointConfig.getEndPoint().get(ForecastType.WEATHER_ALERT); + assertNotNull(alertProperties); + assertEquals("http://example.com", alertProperties.getEndpoint()); + assertEquals("serviceKey", alertProperties.getEncoding()); + assertEquals("serviceKey", alertProperties.getDecoding()); + assertEquals("http://example.com", alertProperties.getSiteUrl()); + } + + @Test + public void testMediumTermWeatherEndpointConfig() { + ApiProperties mediumTermProperties = weatherEndpointConfig.getEndPoint().get(ForecastType.MEDIUM_TERM); + assertNotNull(mediumTermProperties); + assertEquals("http://example.com", mediumTermProperties.getEndpoint()); + assertEquals("serviceKey", mediumTermProperties.getEncoding()); + assertEquals("serviceKey", mediumTermProperties.getDecoding()); + assertEquals("http://example.com", mediumTermProperties.getSiteUrl()); + } + + +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/dto/CourseReqeustTest.java b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/dto/CourseReqeustTest.java new file mode 100644 index 00000000..67abdd8a --- /dev/null +++ b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/dto/CourseReqeustTest.java @@ -0,0 +1,77 @@ +package com.hubo.gillajabi.crawl.infraStructure.dto; + +import com.hubo.gillajabi.crawl.domain.constant.Province; +import com.hubo.gillajabi.crawl.domain.entity.City; +import com.hubo.gillajabi.crawl.domain.entity.CourseTheme; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CityRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.request.CourseThemeRequest; +import com.hubo.gillajabi.crawl.infrastructure.dto.response.ApiCourseResponse; +import com.navercorp.fixturemonkey.FixtureMonkey; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CourseReqeustTest { + + public static FixtureMonkey fixtureMonkey = FixtureMonkey.builder().build(); + + private ApiCourseResponse.Course createApiResponse() { + return fixtureMonkey.giveMeBuilder(ApiCourseResponse.Course.class) + .set("routeIdx", "1") + .set("crsIdx", "1") + .set("crsKorNm", "남파랑길 1코스") + .set("crsDstnc", "5") + .set("crsTotlRqrmHour", "2") + .set("crsLevel", "1") + .set("crsCycle", "비순환형") + .set("crsContents", "남파랑길 1코스는 ...") + .set("crsSummary", "남파랑길 1코스는 ...") + .set("sigun", "경남 김해시") + .sample(); + } + + private City createCity() { + CityRequest cityRequest = CityRequest.of("김해시", Province.GYEONGNAM, "김해"); + return City.createCity(cityRequest); + } + + private static CourseTheme createCourseTheme() { + CourseThemeRequest courseThemeRequest = fixtureMonkey.giveMeOne(CourseThemeRequest.class); + return CourseTheme.createCourseTheme(courseThemeRequest); + } + + @Test + @DisplayName("createCourse에서 잘못된 코스 이름이 들어왔을 때 IllegalArgumentException 발생") + public void createCourse_잘못된_코스_이름_입력시_IllegalArgumentException_발생() { + // given + ApiCourseResponse.Course apiCourseResponse = createApiResponse(); + apiCourseResponse.setCrsKorNm("잘못된이름"); + + // when + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + CourseRequest.of(apiCourseResponse, createCity(), createCourseTheme()); + }); + + // then + String expectedMessage = "잘못된 코스 이름 : " + apiCourseResponse.getCrsKorNm(); + String actualMessage = exception.getMessage(); + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + @DisplayName("createCourse에서 올바른 코스 이름이 들어왔을때 올바르게 통과") + public void createCourse_올바른_코스_이름_입력시(){ + // given + ApiCourseResponse.Course apiCourseResponse = createApiResponse(); + apiCourseResponse.setCrsKorNm("남파랑길 1코스"); + + // when + CourseRequest courseRequest = CourseRequest.of(apiCourseResponse, createCity(), createCourseTheme()); + + // then + assertTrue(courseRequest.getCourseName().equals(apiCourseResponse.getCrsKorNm())); + } +} diff --git a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/CrawlApiBuilderHelperTest.java b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/CrawlApiBuilderHelperTest.java index eb28a2e1..9d4083b0 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/CrawlApiBuilderHelperTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/CrawlApiBuilderHelperTest.java @@ -1,86 +1,125 @@ -package com.hubo.gillajabi.crawl.infraStructure.util.helper; - -import com.hubo.gillajabi.crawl.infrastructure.config.ApiProperties; -import com.hubo.gillajabi.crawl.infrastructure.dto.response.ValidatableResponse; -import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlApiBuilderHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.URI; -import java.net.URISyntaxException; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class CrawlApiBuilderHelperTest { - - private CrawlApiBuilderHelper crawlApiBuilderHelper; - - private ApiProperties apiProperties; - - @Mock - private ValidatableResponse response; - - private static final String ENDPOINT_PATH = "testEndpoint"; - private static final String SITE_URL = "http://example.com/api/{}?serviceKey={serviceKey}&numOfRows={numOfRows}&pageNo={pageNo}"; - private static final String SERVICE_KEY = "testServiceKey"; - private static final int PAGE_NO = 1; - private static final int NUM_OF_ROWS = 10; - - @BeforeEach - public void setUp() { - crawlApiBuilderHelper = new CrawlApiBuilderHelper(); - - apiProperties = new ApiProperties(); - apiProperties.setSiteUrl(SITE_URL); - apiProperties.setEncoding(SERVICE_KEY); - } - - @Test - @DisplayName("buildUri 메서드 호출 시, 올바르게 URI 반환") - public void 유효한_입력일시_올바른_URI_반환() throws URISyntaxException { - // given 유효한 입력 값 - URI expectedUri = new URI("http://example.com/api/testEndpoint?serviceKey=testServiceKey&numOfRows=10&pageNo=1"); - - // when - URI resultUri = crawlApiBuilderHelper.buildUri(ENDPOINT_PATH, apiProperties, PAGE_NO, NUM_OF_ROWS); - - // then: 결과 URI가 예상 URI와 일치하는지 확인 - assertEquals(expectedUri, resultUri); - } - - @Test - @DisplayName("잘못된 변수로 buildUri 메서드 호출 시, URI 생성 실패") - public void 유효하지_않은_입력일시_URI생성실패() { - // given: 잘못된 siteUrl - String invalidSiteUrl = "http://[invalid-url]/api/{}?serviceKey={serviceKey}&numOfRows={numOfRows}&pageNo={pageNo}"; - apiProperties.setSiteUrl(invalidSiteUrl); - - // when - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { - crawlApiBuilderHelper.buildUri(ENDPOINT_PATH, apiProperties, PAGE_NO, NUM_OF_ROWS); - }); - - //then - String expectedMessage = "URI 생성 실패"; - assertTrue(exception.getMessage().contains(expectedMessage)); - } - - @Test - @DisplayName("validateResponse 메서드 호출 시, response.validate() 메서드 호출") - public void validateResponse() { - // given - doNothing().when(response).validate(); - - // when - crawlApiBuilderHelper.validateResponse(response); - - // then - verify(response, times(1)).validate(); - } -} +//package com.hubo.gillajabi.crawl.infraStructure.util.helper; +// +//import com.hubo.gillajabi.crawl.domain.entity.City; +//import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlApiBuilderHelper; +//import com.hubo.gillajabi.global.common.dto.ApiProperties; +//import com.hubo.gillajabi.crawl.infrastructure.dto.response.ValidatableResponse; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.Mock; +//import org.mockito.junit.jupiter.MockitoExtension; +// +//import java.net.URI; +//import java.net.URISyntaxException; +//import java.time.LocalDate; +//import java.time.format.DateTimeFormatter; +// +//import static org.junit.jupiter.api.Assertions.*; +//import static org.mockito.Mockito.*; +// +//@ExtendWith(MockitoExtension.class) +//class CrawlApiBuilderHelperTest { +// +// private CrawlApiBuilderHelper crawlApiBuilderHelper; +// +// private ApiProperties apiProperties; +// +// @Mock +// private ValidatableResponse response; +// +// @Mock +// private City city; +// +// private static final String ENDPOINT_PATH = "testEndpoint"; +// private static final String SITE_URL = "http://example.com/api/{}?serviceKey={serviceKey}&numOfRows={numOfRows}&pageNo={pageNo}"; +// private static final String SERVICE_KEY = "testServiceKey"; +// private static final int PAGE_NO = 1; +// private static final String CITY_CODE = "testCityCode"; +// private static final LocalDate DATE = LocalDate.of(2023, 6, 28); +// private static final int NX = 60; +// private static final int NY = 127; +// +// @BeforeEach +// public void setUp() { +// crawlApiBuilderHelper = new CrawlApiBuilderHelper(); +// +// apiProperties = new ApiProperties(); +// apiProperties.setSiteUrl(SITE_URL); +// apiProperties.setEncoding(SERVICE_KEY); +// +// // when(city.getCityCode()).thenReturn(CITY_CODE); +// } +// +// @Test +// @DisplayName("buildUri 메서드 호출 시, 올바르게 URI 반환") +// public void 유효한_입력일시_올바른_URI_반환() throws URISyntaxException { +// // given +// URI expectedUri = new URI("http://example.com/api/testEndpoint?serviceKey=testServiceKey&numOfRows=100&pageNo=1"); +// +// // when +// URI resultUri = crawlApiBuilderHelper.buildUri(ENDPOINT_PATH, apiProperties, PAGE_NO); +// +// // then +// assertEquals(expectedUri, resultUri); +// } +// +// @Test +// @DisplayName("잘못된 변수로 buildUri 메서드 호출 시, URI 생성 실패") +// public void 유효하지_않은_입력일시_URI생성실패() { +// // given +// String invalidSiteUrl = "http://[invalid-url]/api/{}?serviceKey={serviceKey}&numOfRows={numOfRows}&pageNo={pageNo}"; +// apiProperties.setSiteUrl(invalidSiteUrl); +// +// // when +// IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { +// crawlApiBuilderHelper.buildUri(ENDPOINT_PATH, apiProperties, PAGE_NO); +// }); +// +// //then +// String expectedMessage = "URI 생성 실패"; +// assertTrue(exception.getMessage().contains(expectedMessage)); +// } +// +// @Test +// @DisplayName("validateResponse 메서드 호출 시, response.validate() 메서드 호출") +// public void validateResponse() { +// // given +// doNothing().when(response).validate(); +// +// // when +// crawlApiBuilderHelper.validateResponse(response); +// +// // then +// verify(response, times(1)).validate(); +// } +// +// @Test +// @DisplayName("buildUri (MEDIUM_TERM) 메서드 호출 시, 올바르게 URI 반환") +// public void 유효한_MEDIUM_TERM_입력일시_올바른_URI_반환() throws URISyntaxException { +// // given +// String invalidSiteUrl = "https://apis.data.go.kr/1360000/MidFcstInfoService/{endPath}?serviceKey={serviceKey}&pageNo={pageNo}&numOfRows={numOfRows}&dataType=JSON®Id={cityCode}&tmFc={tmFc} +// apiProperties.setSiteUrl(invalidSiteUrl); +// // when +// URI resultUri = crawlApiBuilderHelper.buildUri(ENDPOINT_PATH, apiProperties, city, PAGE_NO, DATE); +// +// // then +// assertEquals(expectedUri, resultUri); +// } +// +// @Test +// @DisplayName("buildUri (current 날씨) 메서드 호출 시, 올바르게 URI 반환") +// public void 유효한_현재_날씨_입력일시_올바른_URI_반환() throws URISyntaxException { +// // given +// String baseDateStr = DATE.format(DateTimeFormatter.ofPattern("yyyyMMdd")); +// URI expectedUri = new URI("http://example.com/api/testEndpoint?serviceKey=testServiceKey&pageNo=1&numOfRows=100&baseDate=" + baseDateStr + "&nx=" + NX + "&ny=" + NY); +// +// // when +// URI resultUri = crawlApiBuilderHelper.buildUri(apiProperties, NX, NY, DATE, PAGE_NO); +// +// // then +// assertEquals(expectedUri, resultUri); +// } +// +//} diff --git a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/CrawlResponseParserHelperTest.java b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/RoadRoadCrawlResponseParserHelperTest.java similarity index 78% rename from src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/CrawlResponseParserHelperTest.java rename to src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/RoadRoadCrawlResponseParserHelperTest.java index fda1ee8f..af1efcb4 100644 --- a/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/CrawlResponseParserHelperTest.java +++ b/src/test/java/com/hubo/gillajabi/crawl/infraStructure/util/helper/RoadRoadCrawlResponseParserHelperTest.java @@ -1,12 +1,12 @@ package com.hubo.gillajabi.crawl.infraStructure.util.helper; -import com.hubo.gillajabi.crawl.infrastructure.util.helper.CrawlResponseParserHelper; +import com.hubo.gillajabi.crawl.infrastructure.util.helper.RoadCrawlResponseParserHelper; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -public class CrawlResponseParserHelperTest { +public class RoadRoadCrawlResponseParserHelperTest { @Test @DisplayName("경남 김해시 -> 김해시로 파싱") @@ -16,7 +16,7 @@ public class CrawlResponseParserHelperTest { String expected = "김해시"; // When - String result = CrawlResponseParserHelper.parseDuruResponseByCity(input); + String result = RoadCrawlResponseParserHelper.parseDuruResponseByCity(input); // Then assertEquals(expected, result); @@ -30,7 +30,7 @@ public class CrawlResponseParserHelperTest { // When Exception exception = assertThrows(IllegalArgumentException.class, () -> { - CrawlResponseParserHelper.parseDuruResponseByCity(input); + RoadCrawlResponseParserHelper.parseDuruResponseByCity(input); }); // Then @@ -47,7 +47,7 @@ public class CrawlResponseParserHelperTest { String expected = "경남"; // When - String result = CrawlResponseParserHelper.parseDuruResponseByProvince(input); + String result = RoadCrawlResponseParserHelper.parseDuruResponseByProvince(input); // Then assertEquals(expected, result);