diff --git a/build.gradle b/build.gradle index d220628..309d2db 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,9 @@ dependencies { //swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + //crawling + implementation 'org.seleniumhq.selenium:selenium-java:4.14.1' + implementation 'com.opencsv:opencsv:5.7.1' } dependencyManagement { diff --git a/src/main/java/com/catcher/batch/BatchApplication.java b/src/main/java/com/catcher/batch/BatchApplication.java index 452d22e..374b10f 100644 --- a/src/main/java/com/catcher/batch/BatchApplication.java +++ b/src/main/java/com/catcher/batch/BatchApplication.java @@ -3,9 +3,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication @EnableFeignClients(basePackages = "com.catcher.batch.resource.external") +@EnableJpaAuditing public class BatchApplication { public static void main(String[] args) { diff --git a/src/main/java/com/catcher/batch/common/service/CatcherFeignService.java b/src/main/java/com/catcher/batch/common/service/CatcherFeignService.java index ab6b25f..d812a30 100644 --- a/src/main/java/com/catcher/batch/common/service/CatcherFeignService.java +++ b/src/main/java/com/catcher/batch/common/service/CatcherFeignService.java @@ -2,11 +2,13 @@ import com.catcher.batch.core.converter.CatcherConverter; import com.catcher.batch.core.properties.PropertyBase; +import com.catcher.batch.core.properties.HeaderSupport; import com.catcher.batch.resource.external.ExternalFeign; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpHeaders; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -40,6 +42,13 @@ public T parseService(Map params, Class requestType) { property.setParams(params); URI uri = property.getURI(); + // 헤더가 있는 경우 + HttpHeaders headers; + if (property instanceof HeaderSupport headerSupport) { + headers = headerSupport.addHeaders(); + return catcherConverter.parse(externalFeign.getInfoWithHeader(headers.getFirst("Authorization"), uri), requestType); + } + return catcherConverter.parse(externalFeign.getInfo(uri), requestType); } diff --git a/src/main/java/com/catcher/batch/common/utils/HashCodeGenerator.java b/src/main/java/com/catcher/batch/common/utils/HashCodeGenerator.java new file mode 100644 index 0000000..491ac40 --- /dev/null +++ b/src/main/java/com/catcher/batch/common/utils/HashCodeGenerator.java @@ -0,0 +1,9 @@ +package com.catcher.batch.common.utils; + +import org.apache.commons.codec.digest.DigestUtils; + +public class HashCodeGenerator { + public static String hashString(String category, String input) { + return DigestUtils.sha256Hex(category + "-" + input); + } +} diff --git a/src/main/java/com/catcher/batch/core/converter/CatcherConverter.java b/src/main/java/com/catcher/batch/core/converter/CatcherConverter.java index 4b666c9..6129453 100644 --- a/src/main/java/com/catcher/batch/core/converter/CatcherConverter.java +++ b/src/main/java/com/catcher/batch/core/converter/CatcherConverter.java @@ -3,6 +3,7 @@ import com.catcher.batch.annotation.CatcherJson; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; import org.json.JSONObject; import org.springframework.stereotype.Component; @@ -33,6 +34,8 @@ private String getPath(Class responseType) { private JSONObject getJsonObject(String json, String jsonPath) { if(jsonPath == null) { throw new IllegalStateException(); + } else if (StringUtils.isBlank(jsonPath)) { + return new JSONObject(json); } JSONObject jsonObject = new JSONObject(json); diff --git a/src/main/java/com/catcher/batch/core/database/CatcherItemRepository.java b/src/main/java/com/catcher/batch/core/database/CatcherItemRepository.java new file mode 100644 index 0000000..772858a --- /dev/null +++ b/src/main/java/com/catcher/batch/core/database/CatcherItemRepository.java @@ -0,0 +1,17 @@ +package com.catcher.batch.core.database; + +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.domain.entity.Category; + +import java.util.List; +import java.util.Optional; + +public interface CatcherItemRepository { + void saveAll(List catcherItems); + + void save(CatcherItem catcherItem); + + Optional findByItemHashValue(String hashKey); + + List findByCategory(Category category); +} diff --git a/src/main/java/com/catcher/batch/core/database/CategoryRepository.java b/src/main/java/com/catcher/batch/core/database/CategoryRepository.java new file mode 100644 index 0000000..015bddd --- /dev/null +++ b/src/main/java/com/catcher/batch/core/database/CategoryRepository.java @@ -0,0 +1,11 @@ +package com.catcher.batch.core.database; + +import com.catcher.batch.core.domain.entity.Category; + +import java.util.Optional; + +public interface CategoryRepository { + Optional findByName(String name); + + Category save(Category category); +} diff --git a/src/main/java/com/catcher/batch/core/database/LocationRepository.java b/src/main/java/com/catcher/batch/core/database/LocationRepository.java new file mode 100644 index 0000000..872d45c --- /dev/null +++ b/src/main/java/com/catcher/batch/core/database/LocationRepository.java @@ -0,0 +1,9 @@ +package com.catcher.batch.core.database; + +import com.catcher.batch.core.domain.entity.Location; + +import java.util.Optional; + +public interface LocationRepository { + Optional findByDescription(String province, String city); +} diff --git a/src/main/java/com/catcher/batch/core/domain/CatcherItemExecutor.java b/src/main/java/com/catcher/batch/core/domain/CatcherItemExecutor.java new file mode 100644 index 0000000..a3941a5 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/CatcherItemExecutor.java @@ -0,0 +1,12 @@ +package com.catcher.batch.core.domain; + +import com.catcher.batch.core.domain.command.Command; +import org.springframework.stereotype.Component; + +@Component +public class CatcherItemExecutor implements CommandExecutor { + @Override + public T run(Command command) { + return command.execute(); + } +} diff --git a/src/main/java/com/catcher/batch/core/domain/CommandExecutor.java b/src/main/java/com/catcher/batch/core/domain/CommandExecutor.java new file mode 100644 index 0000000..939a173 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/CommandExecutor.java @@ -0,0 +1,7 @@ +package com.catcher.batch.core.domain; + +import com.catcher.batch.core.domain.command.Command; + +public interface CommandExecutor { + T run(Command command); +} diff --git a/src/main/java/com/catcher/batch/core/domain/command/Command.java b/src/main/java/com/catcher/batch/core/domain/command/Command.java new file mode 100644 index 0000000..89b044e --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/command/Command.java @@ -0,0 +1,5 @@ +package com.catcher.batch.core.domain.command; + +public interface Command { + T execute(); +} diff --git a/src/main/java/com/catcher/batch/core/domain/command/RegisterCampingDataCommand.java b/src/main/java/com/catcher/batch/core/domain/command/RegisterCampingDataCommand.java new file mode 100644 index 0000000..d9099ca --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/command/RegisterCampingDataCommand.java @@ -0,0 +1,18 @@ +package com.catcher.batch.core.domain.command; + +import com.catcher.batch.core.dto.CampingApiResponse; +import com.catcher.batch.core.service.CampingService; +import lombok.RequiredArgsConstructor; + + +@RequiredArgsConstructor +public class RegisterCampingDataCommand implements Command { + private final CampingService campingService; + private final CampingApiResponse campingApiResponse; + + @Override + public Void execute() { + campingService.batch(campingApiResponse); + return null; + } +} diff --git a/src/main/java/com/catcher/batch/core/domain/command/RegisterRestaurantDataCommand.java b/src/main/java/com/catcher/batch/core/domain/command/RegisterRestaurantDataCommand.java new file mode 100644 index 0000000..738cb2e --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/command/RegisterRestaurantDataCommand.java @@ -0,0 +1,17 @@ +package com.catcher.batch.core.domain.command; + +import com.catcher.batch.core.dto.RestaurantApiResponse; +import com.catcher.batch.core.service.RestaurantService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class RegisterRestaurantDataCommand implements Command { + private final RestaurantService restaurantService; + private final RestaurantApiResponse restaurantApiResponse; + + @Override + public Void execute() { + restaurantService.batch(restaurantApiResponse); + return null; + } +} diff --git a/src/main/java/com/catcher/batch/core/domain/entity/BaseTimeEntity.java b/src/main/java/com/catcher/batch/core/domain/entity/BaseTimeEntity.java new file mode 100644 index 0000000..fa0f69f --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/entity/BaseTimeEntity.java @@ -0,0 +1,32 @@ +package com.catcher.batch.core.domain.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; + +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +@Getter +public class BaseTimeEntity { + @Column(name = "created_at") + private ZonedDateTime createdAt; + + @Column(name = "updated_at") + private ZonedDateTime updatedAt; + + @PrePersist + private void prePersist() { + this.createdAt = ZonedDateTime.now(); + this.updatedAt = ZonedDateTime.now(); + } + + @PreUpdate + private void preUpdate() { + this.updatedAt = ZonedDateTime.now(); + } +} diff --git a/src/main/java/com/catcher/batch/core/domain/entity/CatcherItem.java b/src/main/java/com/catcher/batch/core/domain/entity/CatcherItem.java new file mode 100644 index 0000000..a10abcf --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/entity/CatcherItem.java @@ -0,0 +1,49 @@ +package com.catcher.batch.core.domain.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.Where; + +import java.time.ZonedDateTime; + +@Entity +@Builder +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Where(clause = "deleted_at IS NULL") +@Table(name = "catcher_item") +public class CatcherItem extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + private Category category; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "location_id") + private Location location; + + @Column(unique = true, nullable = false) + private String itemHashValue; + + @Column(nullable = false) + private String title; + + private String description; + + private String thumbnailUrl; + + private String resourceUrl; + + @Column(name = "start_at") + private ZonedDateTime startAt; + + @Column(name = "end_at") + private ZonedDateTime endAt; + + @Column(name = "deleted_at") + private ZonedDateTime deletedAt; +} diff --git a/src/main/java/com/catcher/batch/core/domain/entity/Category.java b/src/main/java/com/catcher/batch/core/domain/entity/Category.java new file mode 100644 index 0000000..2f96783 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/entity/Category.java @@ -0,0 +1,26 @@ +package com.catcher.batch.core.domain.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "category") +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + public static Category create(String name) { + Category category = new Category(); + category.name = name; + return category; + } +} diff --git a/src/main/java/com/catcher/batch/core/domain/entity/Location.java b/src/main/java/com/catcher/batch/core/domain/entity/Location.java new file mode 100644 index 0000000..99995eb --- /dev/null +++ b/src/main/java/com/catcher/batch/core/domain/entity/Location.java @@ -0,0 +1,24 @@ +package com.catcher.batch.core.domain.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "location") +public class Location { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String areaCode; + + private String latitude; + + private String longitude; + + private String description; +} diff --git a/src/main/java/com/catcher/batch/core/dto/CampingApiResponse.java b/src/main/java/com/catcher/batch/core/dto/CampingApiResponse.java new file mode 100644 index 0000000..042fecc --- /dev/null +++ b/src/main/java/com/catcher/batch/core/dto/CampingApiResponse.java @@ -0,0 +1,74 @@ +package com.catcher.batch.core.dto; + +import com.catcher.batch.annotation.CatcherJson; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.util.List; + +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +@CatcherJson(path = "response.body") +public class CampingApiResponse { + + @JsonProperty("items") + private CampingItems items; + + @JsonProperty("totalCount") + private Integer totalCount; + + @JsonProperty("numOfRows") + private Integer numOfRows; + + @JsonProperty("pageNo") + private Integer pageNo; + + @Getter + @JsonIgnoreProperties(ignoreUnknown = true) + public static class CampingItems { + @JsonProperty("item") + private List item; + } + + @Getter + @JsonIgnoreProperties(ignoreUnknown = true) + public static class CampingItem { + + @JsonProperty("facltNm") + private String name; + + @JsonProperty("contentId") + private String key; + + @JsonProperty("lineIntro") + private String description; + + @JsonProperty("doNm") + private String province; + + @JsonProperty("sigunguNm") + private String city; + + @JsonProperty("addr1") + private String address; + + @JsonProperty("induty") + private String category; + + @JsonProperty("zipcode") + private String zipcode; + + @JsonProperty("homepage") + private String homepage; + + @JsonProperty("mapX") + private String latitude; + + @JsonProperty("mapY") + private String longitude; + + @JsonProperty("firstImageUrl") + private String thumbnailUrl; + } +} diff --git a/src/main/java/com/catcher/batch/core/dto/RestaurantApiResponse.java b/src/main/java/com/catcher/batch/core/dto/RestaurantApiResponse.java new file mode 100644 index 0000000..045d7b4 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/dto/RestaurantApiResponse.java @@ -0,0 +1,57 @@ +package com.catcher.batch.core.dto; + +import com.catcher.batch.annotation.CatcherJson; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.util.List; + +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +@CatcherJson(path = "") +public class RestaurantApiResponse { + + @JsonProperty("meta") + private Meta meta; + + @JsonProperty("documents") + private List items; + + @Getter + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Meta { + + @JsonProperty("pageable_count") + private int pageableCount; + + @JsonProperty("total_count") + private int totalCount; + + @JsonProperty("is_end") + private boolean isEnd; + } + + @Getter + @JsonIgnoreProperties(ignoreUnknown = true) + public static class RestaurantItem { + + @JsonProperty("id") + private String key; + + @JsonProperty("place_name") + private String name; + + @JsonProperty("place_url") + private String resourceUrl; + + @JsonProperty("address_name") + private String address; + + @JsonProperty("x") + private String latitude; + + @JsonProperty("y") + private String longitude; + } +} diff --git a/src/main/java/com/catcher/batch/core/properties/HeaderPropertyProxy.java b/src/main/java/com/catcher/batch/core/properties/HeaderPropertyProxy.java new file mode 100644 index 0000000..fc200b5 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/properties/HeaderPropertyProxy.java @@ -0,0 +1,37 @@ +package com.catcher.batch.core.properties; + +import org.springframework.http.HttpHeaders; + +import java.net.URI; +import java.util.Map; + +public class HeaderPropertyProxy extends PropertyBase { + private final PropertyBase property; + + public HeaderPropertyProxy(PropertyBase property) { + super(property.getEndPoint()); + this.property = property; + } + + @Override + public boolean support(Class clazz) { + return property.support(clazz); + } + + @Override + public URI getURI() { + return property.getURI(); + } + + @Override + public void setParams(Map params) { + property.setParams(params); + } + + public HttpHeaders addHeaders() { + if (property instanceof HeaderSupport) { + return ((HeaderSupport) property).addHeaders(); + } + return new HttpHeaders(); + } +} diff --git a/src/main/java/com/catcher/batch/core/properties/HeaderSupport.java b/src/main/java/com/catcher/batch/core/properties/HeaderSupport.java new file mode 100644 index 0000000..9b6d3b6 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/properties/HeaderSupport.java @@ -0,0 +1,7 @@ +package com.catcher.batch.core.properties; + +import org.springframework.http.HttpHeaders; + +public interface HeaderSupport { + HttpHeaders addHeaders(); +} diff --git a/src/main/java/com/catcher/batch/core/service/CampingService.java b/src/main/java/com/catcher/batch/core/service/CampingService.java new file mode 100644 index 0000000..4edc71e --- /dev/null +++ b/src/main/java/com/catcher/batch/core/service/CampingService.java @@ -0,0 +1,68 @@ +package com.catcher.batch.core.service; + +import com.catcher.batch.core.database.CatcherItemRepository; +import com.catcher.batch.core.database.CategoryRepository; +import com.catcher.batch.core.database.LocationRepository; +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.domain.entity.Category; +import com.catcher.batch.core.domain.entity.Location; +import com.catcher.batch.core.dto.CampingApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.catcher.batch.common.utils.HashCodeGenerator.hashString; + +@Service +@RequiredArgsConstructor +public class CampingService { + private final CatcherItemRepository catcherItemRepository; + private final CategoryRepository categoryRepository; + private final LocationRepository locationRepository; + public static final String CATEGORY_NAME = "camping"; + + @Transactional + public void batch(CampingApiResponse campingApiResponse) { + Category category = categoryRepository.findByName(CATEGORY_NAME) + .orElseGet(() -> categoryRepository.save(Category.create(CATEGORY_NAME))); + + Map itemMap = catcherItemRepository.findByCategory(category).stream() + .collect(Collectors.toMap(CatcherItem::getItemHashValue, CatcherItem::getTitle)); + + List campingItems = campingApiResponse.getItems().getItem(); + + List catcherItems = campingItems.stream() + .filter(campingItem -> !itemMap.containsKey(hashString(CATEGORY_NAME, campingItem.getKey()))) + .map(campingItem -> { + Location location = getLocationByDescription(campingItem.getProvince(), campingItem.getCity()); + String hashKey = hashString(CATEGORY_NAME, campingItem.getKey()); + + itemMap.put(hashKey, campingItem.getName()); + + return CatcherItem.builder() + .category(category) + .location(location) + .title(campingItem.getName()) + .description(campingItem.getDescription()) + .thumbnailUrl(campingItem.getThumbnailUrl()) + .itemHashValue(hashKey) + .build(); + }) + .collect(Collectors.toList()); + + if (!catcherItems.isEmpty()) { + catcherItemRepository.saveAll(catcherItems); + } + } + + private Location getLocationByDescription(String province, String city) { + String withoutDo = province.replace("도", ""); + + return locationRepository.findByDescription(withoutDo, city) + .orElseThrow(); + } +} diff --git a/src/main/java/com/catcher/batch/core/service/ExhibitionService.java b/src/main/java/com/catcher/batch/core/service/ExhibitionService.java new file mode 100644 index 0000000..c3ad2a4 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/service/ExhibitionService.java @@ -0,0 +1,131 @@ +package com.catcher.batch.core.service; + +import com.catcher.batch.core.database.CatcherItemRepository; +import com.catcher.batch.core.database.CategoryRepository; +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.domain.entity.Category; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.catcher.batch.common.utils.HashCodeGenerator.hashString; + +@Service +@RequiredArgsConstructor +public class ExhibitionService { + private final CatcherItemRepository catcherItemRepository; + private final CategoryRepository categoryRepository; + public static final String CATEGORY_NAME = "exhibition"; + + public List exhibitionCrawling() { + List catcherItems = new ArrayList<>(); + Category category = categoryRepository.findByName("exhibition") + .orElseGet(() -> categoryRepository.save(Category.create("exhibition"))); + + //TODO 드라이버 경로 상황에 맞게 설정, 그에 따른 크롬 및 gradle 의존성 버전도 알맞게 수정 + System.setProperty("webdriver.chrome.driver", "C:/Users/dong/Downloads/chromedriver-win64/chromedriver-win64/chromedriver.exe"); + + WebDriver driver = new ChromeDriver(); + + //TODO 파라미터 받아서 월 조절하는 것으로 변경 + driver.get("https://www.akei.or.kr/bbs/board.php?bo_table=schedule"); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(20)); + + int page = 1; + + ObjectMapper mapper = new ObjectMapper(); + List exhibition_list = new ArrayList<>(); + + String[] columns = {"engName", "abbreviationName", "host", "period", "location", "field", "homepage", "thumbnailUrl"}; + + while (true) { + WebElement exhibitList = driver.findElement(By.cssSelector(".exhibit_list")); + List contentElements = exhibitList.findElements(By.cssSelector(".content_sc_li")); + + for (WebElement contentElement : contentElements) { + String thumbnailUrl = contentElement.findElement(By.cssSelector("div.schedule_view div.img img")).getAttribute("src"); + String title = contentElement.findElement(By.cssSelector("div.txt strong p")).getText(); + + // 제목에서 인증전시회\n 2023 서울국제소싱페어 이런 케이스 제거 + if (title.contains("\n")) { + title = title.substring(title.indexOf("\n") + 1); + } + + WebElement tbody = contentElement.findElement(By.tagName("tbody")); + List tr_elements = tbody.findElements(By.tagName("tr")); + + List data = new ArrayList<>(); + ObjectNode json = mapper.createObjectNode(); + + for (WebElement td_element : tr_elements) { + WebElement element = td_element.findElement(By.tagName("td")); + data.add(element.getAttribute("innerText")); + } + for (int i = 0; i < columns.length - 1; i++) { + json.put(columns[i], data.get(i)); + } + json.put(columns[7], thumbnailUrl); + json.put(columns[0], title); + exhibition_list.add(json); + } + + page++; + + boolean nextPageExists = isNextPageExists(driver, page); + if (!nextPageExists) { + break; + } + + // 다음 페이지 2,3,4... + WebElement nextPage = driver.findElement(By.xpath("//a[@class='pg_page' and text()='" + page + "']")); + + nextPage.click(); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(20)); + } + //TODO driver.quit 에러 안나는 방법 서치 +// driver.quit(); + + for (ObjectNode exhibitionInfo : exhibition_list) { + CatcherItem catcherItem = CatcherItem.builder() + .category(category) + .title(exhibitionInfo.get("engName").asText()) + .itemHashValue(hashString(CATEGORY_NAME, exhibitionInfo.get("abbreviationName").asText())) + .description(exhibitionInfo.get("period").asText()) + .resourceUrl(exhibitionInfo.get("homepage").asText()) + .thumbnailUrl(exhibitionInfo.get("thumbnailUrl").asText()) + .build(); + + catcherItems.add(catcherItem); + } + // 사이트 자체에 중복 데이터가 있기 때문에 hashValue 기준으로 제거 + List uniqueCatcherItems = catcherItems.stream() + .collect(Collectors.toMap(CatcherItem::getItemHashValue, Function.identity(), (existing, replacement) -> existing)) + .values().stream() + .collect(Collectors.toList()); + + catcherItemRepository.saveAll(uniqueCatcherItems); + + return uniqueCatcherItems; + } + + // 전시회 사이트 다음페이지 여부 검사 근데 어차피 다른 페이지에는 못써서 안에 넣어야할듯? + private static boolean isNextPageExists(WebDriver driver, int nextPage) { + try { + driver.findElement(By.xpath("//a[@class='pg_page' and text()='" + nextPage + "']")); + return true; + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/catcher/batch/core/service/RestaurantService.java b/src/main/java/com/catcher/batch/core/service/RestaurantService.java new file mode 100644 index 0000000..dc8e868 --- /dev/null +++ b/src/main/java/com/catcher/batch/core/service/RestaurantService.java @@ -0,0 +1,68 @@ +package com.catcher.batch.core.service; + +import com.catcher.batch.core.database.CatcherItemRepository; +import com.catcher.batch.core.database.CategoryRepository; +import com.catcher.batch.core.database.LocationRepository; +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.domain.entity.Category; +import com.catcher.batch.core.domain.entity.Location; +import com.catcher.batch.core.dto.RestaurantApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.catcher.batch.common.utils.HashCodeGenerator.hashString; + +@Service +@RequiredArgsConstructor +public class RestaurantService { + private final CatcherItemRepository catcherItemRepository; + private final CategoryRepository categoryRepository; + private final LocationRepository locationRepository; + public static final String CATEGORY_NAME = "restaurant"; + + @Transactional + public void batch(RestaurantApiResponse restaurantApiResponse) { + Category category = categoryRepository.findByName(CATEGORY_NAME) + .orElseGet(() -> categoryRepository.save(Category.create(CATEGORY_NAME))); + + Map itemMap = catcherItemRepository.findByCategory(category).stream() + .collect(Collectors.toMap(CatcherItem::getItemHashValue, CatcherItem::getTitle)); + + List catcherItems = restaurantApiResponse.getItems().stream() + .filter(item -> !itemMap.containsKey(hashString(CATEGORY_NAME, item.getKey()))) + .map(item -> { + Location location = getLocation(item.getAddress()); + String hashKey = hashString(CATEGORY_NAME, item.getKey()); + + itemMap.put(hashKey, item.getName()); + + return CatcherItem.builder() + .category(category) + .location(location) + .title(item.getName()) + .resourceUrl(item.getResourceUrl()) + .itemHashValue(hashKey) + .build(); + }) + .collect(Collectors.toList()); + + if (!catcherItems.isEmpty()) { + catcherItemRepository.saveAll(catcherItems); + } + } + + private Location getLocation(String address) { + String[] parts = address.split("\\s+"); + + String province = parts[0]; + String city = parts[1]; + + return locationRepository.findByDescription(province, city) + .orElseThrow(); + } +} diff --git a/src/main/java/com/catcher/batch/datasource/CatcherItemJpaRepository.java b/src/main/java/com/catcher/batch/datasource/CatcherItemJpaRepository.java new file mode 100644 index 0000000..51d3bfe --- /dev/null +++ b/src/main/java/com/catcher/batch/datasource/CatcherItemJpaRepository.java @@ -0,0 +1,14 @@ +package com.catcher.batch.datasource; + +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.domain.entity.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface CatcherItemJpaRepository extends JpaRepository { + Optional findByItemHashValue(String itemHashValue); + + List findByCategory(Category category); +} diff --git a/src/main/java/com/catcher/batch/datasource/CatcherItemRepositoryImpl.java b/src/main/java/com/catcher/batch/datasource/CatcherItemRepositoryImpl.java new file mode 100644 index 0000000..ff48699 --- /dev/null +++ b/src/main/java/com/catcher/batch/datasource/CatcherItemRepositoryImpl.java @@ -0,0 +1,36 @@ +package com.catcher.batch.datasource; + +import com.catcher.batch.core.database.CatcherItemRepository; +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.domain.entity.Category; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class CatcherItemRepositoryImpl implements CatcherItemRepository { + private final CatcherItemJpaRepository catcherItemJpaRepository; + + @Override + public void saveAll(List catcherItems) { + catcherItemJpaRepository.saveAll(catcherItems); + } + + @Override + public void save(CatcherItem catcherItem) { + catcherItemJpaRepository.save(catcherItem); + } + + @Override + public Optional findByItemHashValue(String hashKey) { + return catcherItemJpaRepository.findByItemHashValue(hashKey); + } + + @Override + public List findByCategory(Category category) { + return catcherItemJpaRepository.findByCategory(category); + } +} diff --git a/src/main/java/com/catcher/batch/datasource/CategoryJpaRepository.java b/src/main/java/com/catcher/batch/datasource/CategoryJpaRepository.java new file mode 100644 index 0000000..c37e7e8 --- /dev/null +++ b/src/main/java/com/catcher/batch/datasource/CategoryJpaRepository.java @@ -0,0 +1,10 @@ +package com.catcher.batch.datasource; + +import com.catcher.batch.core.domain.entity.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CategoryJpaRepository extends JpaRepository { + Optional findByName(String name); +} diff --git a/src/main/java/com/catcher/batch/datasource/CategoryRepositoryImpl.java b/src/main/java/com/catcher/batch/datasource/CategoryRepositoryImpl.java new file mode 100644 index 0000000..32791ab --- /dev/null +++ b/src/main/java/com/catcher/batch/datasource/CategoryRepositoryImpl.java @@ -0,0 +1,24 @@ +package com.catcher.batch.datasource; + +import com.catcher.batch.core.database.CategoryRepository; +import com.catcher.batch.core.domain.entity.Category; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class CategoryRepositoryImpl implements CategoryRepository { + private final CategoryJpaRepository categoryJpaRepository; + + @Override + public Optional findByName(String name) { + return categoryJpaRepository.findByName(name); + } + + @Override + public Category save(Category category) { + return categoryJpaRepository.save(category); + } +} diff --git a/src/main/java/com/catcher/batch/datasource/LocationJpaRepository.java b/src/main/java/com/catcher/batch/datasource/LocationJpaRepository.java new file mode 100644 index 0000000..3d9ba71 --- /dev/null +++ b/src/main/java/com/catcher/batch/datasource/LocationJpaRepository.java @@ -0,0 +1,10 @@ +package com.catcher.batch.datasource; + +import com.catcher.batch.core.domain.entity.Location; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface LocationJpaRepository extends JpaRepository { + Optional findByDescriptionStartingWithAndDescriptionEndingWith(String start, String end); +} diff --git a/src/main/java/com/catcher/batch/datasource/LocationRepositoryImpl.java b/src/main/java/com/catcher/batch/datasource/LocationRepositoryImpl.java new file mode 100644 index 0000000..28c55b3 --- /dev/null +++ b/src/main/java/com/catcher/batch/datasource/LocationRepositoryImpl.java @@ -0,0 +1,19 @@ +package com.catcher.batch.datasource; + +import com.catcher.batch.core.database.LocationRepository; +import com.catcher.batch.core.domain.entity.Location; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class LocationRepositoryImpl implements LocationRepository { + private final LocationJpaRepository locationJpaRepository; + + @Override + public Optional findByDescription(String start, String end) { + return locationJpaRepository.findByDescriptionStartingWithAndDescriptionEndingWith(start, end); + } +} diff --git a/src/main/java/com/catcher/batch/datasource/config/DatabaseConfiguration.java b/src/main/java/com/catcher/batch/datasource/config/DatabaseConfiguration.java index f04cd47..6c8541b 100644 --- a/src/main/java/com/catcher/batch/datasource/config/DatabaseConfiguration.java +++ b/src/main/java/com/catcher/batch/datasource/config/DatabaseConfiguration.java @@ -53,32 +53,27 @@ public class DatabaseConfiguration { @Bean public DataSource dataSource() throws Exception { // KMS 활용한 연결 -// JSch jsch = new JSch(); -// Session session = jsch.getSession( -// KmsUtils.decrypt(sshUsername), -// KmsUtils.decrypt(sshHost), -// sshPort -// ); -// session.setPassword(KmsUtils.decrypt(sshPassword)); -// session.setConfig("StrictHostKeyChecking", "no"); -// session.connect(); -// -// int assignedPort = session.setPortForwardingL(0, -// KmsUtils.decrypt(originUrl), -// localPort -// ); // TODO: lport 값(현재 0)은 추후 서버 올릴때는 지정해줘야함 -// -// return DataSourceBuilder.create() -// .url(KmsUtils.decrypt(databaseUrl).replace(Integer.toString(localPort), Integer.toString(assignedPort))) -// .username(KmsUtils.decrypt(databaseUsername)) -// .password(KmsUtils.decrypt(databasePassword)) -// .build(); - // EKS ConfigMap & Secret + JSch jsch = new JSch(); + Session session = jsch.getSession( + KmsUtils.decrypt(sshUsername), + KmsUtils.decrypt(sshHost), + sshPort + ); + session.setPassword(KmsUtils.decrypt(sshPassword)); + session.setConfig("StrictHostKeyChecking", "no"); + session.connect(); + + int assignedPort = session.setPortForwardingL(0, + KmsUtils.decrypt(originUrl), + localPort + ); // TODO: lport 값(현재 0)은 추후 서버 올릴때는 지정해줘야함 + DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driverClassName); - dataSource.setUrl(databaseUrl); - dataSource.setUsername(databaseUsername); - dataSource.setPassword(databasePassword); + dataSource.setUrl(KmsUtils.decrypt(databaseUrl).replace(Integer.toString(localPort), Integer.toString(assignedPort))); + dataSource.setUsername(KmsUtils.decrypt(databaseUsername)); + dataSource.setPassword(KmsUtils.decrypt(databasePassword)); + return dataSource; } } diff --git a/src/main/java/com/catcher/batch/infrastructure/properties/CampingProperties.java b/src/main/java/com/catcher/batch/infrastructure/properties/CampingProperties.java new file mode 100644 index 0000000..c226dcc --- /dev/null +++ b/src/main/java/com/catcher/batch/infrastructure/properties/CampingProperties.java @@ -0,0 +1,57 @@ +package com.catcher.batch.infrastructure.properties; + +import com.catcher.batch.core.dto.CampingApiResponse; +import com.catcher.batch.core.properties.PropertyBase; +import com.catcher.batch.infrastructure.utils.KmsUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.Map; + +@Component +public class CampingProperties extends PropertyBase { + + @Value("${camping.key}") + private String serviceKey; + + public CampingProperties(@Value("${camping.baseUrl}") String endPoint) { + super(endPoint); + } + + @Override + public boolean support(Class clazz) { + return clazz.isAssignableFrom(CampingApiResponse.class); + } + + @Override + public URI getURI() { + try { + String key = URLEncoder.encode(KmsUtils.decrypt(serviceKey), "UTF-8"); + + UriComponentsBuilder uriBuilder = UriComponentsBuilder + .fromUriString(this.getEndPoint()) + .queryParam("MobileOS", "ETC") + .queryParam("MobileApp", "AppTest") + .queryParam("serviceKey", key) + .queryParam("_type", "json"); + + return this.addParams(uriBuilder) + .build(true).toUri(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private UriComponentsBuilder addParams(UriComponentsBuilder uriComponentsBuilder ) { + Map params = getParams(); + for (String key : params.keySet()) { + uriComponentsBuilder + .queryParam(key, params.get(key)); + } + return uriComponentsBuilder; + } +} diff --git a/src/main/java/com/catcher/batch/infrastructure/properties/RestaurantProperties.java b/src/main/java/com/catcher/batch/infrastructure/properties/RestaurantProperties.java new file mode 100644 index 0000000..36ef559 --- /dev/null +++ b/src/main/java/com/catcher/batch/infrastructure/properties/RestaurantProperties.java @@ -0,0 +1,57 @@ +package com.catcher.batch.infrastructure.properties; + +import com.catcher.batch.core.dto.RestaurantApiResponse; +import com.catcher.batch.core.properties.HeaderSupport; +import com.catcher.batch.core.properties.PropertyBase; +import com.catcher.batch.infrastructure.utils.KmsUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.util.Map; + +@Component +public class RestaurantProperties extends PropertyBase implements HeaderSupport { + + @Value("${restaurant.key}") + private String serviceKey; + + public RestaurantProperties(@Value("${restaurant.baseUrl}") String endPoint) { + super(endPoint); + } + + @Override + public boolean support(Class clazz) { + return clazz.isAssignableFrom(RestaurantApiResponse.class); + } + + @Override + public URI getURI() { + UriComponentsBuilder uriBuilder = UriComponentsBuilder + .fromUriString(this.getEndPoint()) + .queryParam("category_group_code", "FD6"); + + return this.addParams(uriBuilder) + .build().toUri(); + } + + private UriComponentsBuilder addParams(UriComponentsBuilder uriComponentsBuilder ) { + Map params = getParams(); + for (String key : params.keySet()) { + uriComponentsBuilder + .queryParam(key, params.get(key)); + } + return uriComponentsBuilder; + } + + @Override + public HttpHeaders addHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + KmsUtils.decrypt(serviceKey)); + headers.setContentType(MediaType.APPLICATION_JSON); + return headers; + } +} diff --git a/src/main/java/com/catcher/batch/infrastructure/utils/KmsUtils.java b/src/main/java/com/catcher/batch/infrastructure/utils/KmsUtils.java index 26f4136..cc00443 100644 --- a/src/main/java/com/catcher/batch/infrastructure/utils/KmsUtils.java +++ b/src/main/java/com/catcher/batch/infrastructure/utils/KmsUtils.java @@ -32,6 +32,7 @@ public class KmsUtils { public static String encrypt(String text) { try { AWSKMS kmsClient = AWSKMSClientBuilder.standard() + .withRegion(Regions.AP_NORTHEAST_2) .withCredentials(DefaultAWSCredentialsProviderChain.getInstance()) .build(); @@ -50,6 +51,7 @@ public static String encrypt(String text) { public static String decrypt(String cipherBase64) { try { AWSKMS kmsClient = AWSKMSClientBuilder.standard() + .withRegion(Regions.AP_NORTHEAST_2) .withCredentials(DefaultAWSCredentialsProviderChain.getInstance()) .build(); diff --git a/src/main/java/com/catcher/batch/resource/CampingController.java b/src/main/java/com/catcher/batch/resource/CampingController.java new file mode 100644 index 0000000..64ef054 --- /dev/null +++ b/src/main/java/com/catcher/batch/resource/CampingController.java @@ -0,0 +1,50 @@ +package com.catcher.batch.resource; + +import com.catcher.batch.common.response.CommonResponse; +import com.catcher.batch.common.service.CatcherFeignService; +import com.catcher.batch.core.domain.CommandExecutor; +import com.catcher.batch.core.domain.command.RegisterCampingDataCommand; +import com.catcher.batch.core.dto.CampingApiResponse; +import com.catcher.batch.core.service.CampingService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.util.HashMap; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/camping") +@EnableWebMvc +public class CampingController { + private final CatcherFeignService catcherFeignService; + private final CampingService campingService; + private final CommandExecutor commandExecutor; + + @GetMapping("/feign-batch") + public CommonResponse getCampingDataByFeign( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "5") Integer count + ) { + HashMap params = new HashMap<>(); + params.put("pageNo", page); + params.put("numOfRows", count); + CampingApiResponse campingApiResponse = catcherFeignService.parseService(params, CampingApiResponse.class); + + return CommonResponse.success(200, campingApiResponse); + } + + @PostMapping("/batch") + public CommonResponse batchCampingData( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "5") Integer count + ) { + HashMap params = new HashMap<>(); + params.put("pageNo", page); + params.put("numOfRows", count); + CampingApiResponse campingApiResponse = catcherFeignService.parseService(params, CampingApiResponse.class); + + commandExecutor.run(new RegisterCampingDataCommand(campingService, campingApiResponse)); + return CommonResponse.success(201, null); + } +} diff --git a/src/main/java/com/catcher/batch/resource/CrawlerController.java b/src/main/java/com/catcher/batch/resource/CrawlerController.java new file mode 100644 index 0000000..477e3c7 --- /dev/null +++ b/src/main/java/com/catcher/batch/resource/CrawlerController.java @@ -0,0 +1,26 @@ +package com.catcher.batch.resource; + +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.service.ExhibitionService; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/crawler") +public class CrawlerController { + + private final ExhibitionService exhibitionService; + + @GetMapping("/exhibition") + public ResponseEntity> getExhibitionData() { + List exhibitionCrawlingResponse = exhibitionService.exhibitionCrawling(); + return ResponseEntity.ok(exhibitionCrawlingResponse); + } +} diff --git a/src/main/java/com/catcher/batch/resource/RestaurantController.java b/src/main/java/com/catcher/batch/resource/RestaurantController.java new file mode 100644 index 0000000..2bf5f36 --- /dev/null +++ b/src/main/java/com/catcher/batch/resource/RestaurantController.java @@ -0,0 +1,38 @@ +package com.catcher.batch.resource; + +import com.catcher.batch.common.response.CommonResponse; +import com.catcher.batch.common.service.CatcherFeignService; +import com.catcher.batch.core.domain.CommandExecutor; +import com.catcher.batch.core.domain.command.RegisterRestaurantDataCommand; +import com.catcher.batch.core.dto.RestaurantApiResponse; +import com.catcher.batch.core.service.RestaurantService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/restaurant") +public class RestaurantController { + private final CatcherFeignService catcherFeignService; + private final RestaurantService restaurantService; + private final CommandExecutor commandExecutor; + + @PostMapping("/batch") + public CommonResponse batchRestaurantData( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "5") Integer count + ) { + HashMap params = new HashMap<>(); + params.put("page", page); + params.put("size", count); + RestaurantApiResponse restaurantApiResponse = catcherFeignService.parseService(params, RestaurantApiResponse.class); + + commandExecutor.run(new RegisterRestaurantDataCommand(restaurantService, restaurantApiResponse)); + return CommonResponse.success(201, null); + } +} diff --git a/src/main/java/com/catcher/batch/resource/external/ExternalFeign.java b/src/main/java/com/catcher/batch/resource/external/ExternalFeign.java index b79ac64..f48c163 100644 --- a/src/main/java/com/catcher/batch/resource/external/ExternalFeign.java +++ b/src/main/java/com/catcher/batch/resource/external/ExternalFeign.java @@ -2,6 +2,7 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; import java.net.URI; @@ -10,4 +11,6 @@ public interface ExternalFeign { @GetMapping String getInfo(URI url); + @GetMapping + String getInfoWithHeader(@RequestHeader("Authorization") String authorizationHeader, URI url); } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index eee38e3..568f206 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -2,21 +2,20 @@ spring.datasource.driver-class-name=org.mariadb.jdbc.Driver ## spring datasource spring.datasource.url=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AEZqo9kRIKYisZIm0Z0xpuOAAAAiDCBhQYJKoZIhvcNAQcGoHgwdgIBADBxBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDMoZI5smgDwlG+0kBgIBEIBE1VE7k3neYIx1Pkx6YqUrTf7LEBAxddoUbZVTZxC7HQJpNJKNlwMoaXXyHdPTkBW15dJiCw5jUzuY9CRV7o/vpogIg9Q= -spring.datasource.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGLdMIjOGdWmBOvj9YZ5685AAAAajBoBgkqhkiG9w0BBwagWzBZAgEAMFQGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMDgyL4QtIvG2hM9/5AgEQgCdVSGC6ySfTUn8ZyXKzj2x9eFu+05ACK2sP55fJtoGP0hncckeuVmQ= -spring.datasource.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGVzdO6dwAvzNw42TIj6JZ1AAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMjonbIr69Og3D+ujOAgEQgCatIXU02fuulW5mm6Bn+X9e/FPMq3Fk/GhOLyYpudTHfSkRtMORQQ== +spring.datasource.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AFDjW/RUZSn5wxJhd1XeZOgAAAAYzBhBgkqhkiG9w0BBwagVDBSAgEAME0GCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM0eyz4OI2Vkkc/uTUAgEQgCAXcQUOKDsI26jXFzQfba7Cv2EHO51213zwOT0hzc0SnQ== +spring.datasource.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGiM8q8PoYW89muC9gXzZ5GAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMxGIq7N/hB2XW5NODAgEQgCZeBBgSs9LrXXuCsMAHE53Rsg1WGKfDzjMaAoFRDjEKoGTT9uFThQ== ## ssh -ssh.host=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGU6PSF+1N5z7We+p8jZ0o0AAAAaDBmBgkqhkiG9w0BBwagWTBXAgEAMFIGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM9b3AkdokJsb66zbHAgEQgCXIPbqF5l817FjPbh2LXBzXndYJiSUqyDGZXzyfppZi3qqlSYZV -ssh.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AE68oCmRn5p/q+G/NzwrKpHAAAAYjBgBgkqhkiG9w0BBwagUzBRAgEAMEwGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMCABMThPwxXQHcl5bAgEQgB8+W4uWWbUm8bpo7QTCtjYyJ/ZB5hv4SKstc3Xa4Ltt -ssh.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGlm0DJ1SuCTcwtPCyXZLYjAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM1awvQRELJm1vQL/oAgEQgCasN+qZQ0q7w4FOhwRTCzPYzoMnraA1yx4T9hz2U3j4SGn3+3yEaQ== -ssh.datasource.origin=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGuDWPowi/9gzZpLApcSpl9AAAAmzCBmAYJKoZIhvcNAQcGoIGKMIGHAgEAMIGBBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNqU18Wps+61WNJv6QIBEIBUziHPbeZlunGk/0EvzFYVr2TG4HYuwKd9bQcUdRL7PPkZqO+zdFXeYRtgfhyq/isQqKav0E7LAsnrruFaM0BfGmoqHChECFdSABUJ7+uSdWF2F/vV +ssh.host=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGc1mlusKIbT/FmW65+2rl4AAAAbDBqBgkqhkiG9w0BBwagXTBbAgEAMFYGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMvijHTTZUUuGjjF4jAgEQgCmKq00a3J3yAUkdljTk342nBjYaeLr5BQeJ2kIISM3P/WPMXKQB/0WzlQ== +ssh.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AE26DRJsDEHLdAnA5QIkHfYAAAAZjBkBgkqhkiG9w0BBwagVzBVAgEAMFAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMd4p4zWU1dg+tbLAKAgEQgCNWOoyj2Ual/lQaCe7yz2JkY6TXbUR25FO6SkPkYaAncbgkcA== +ssh.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AFNH5Ud+NVY/FVqOa99UOeMAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMsRopSwW2oDrqOTNrAgEQgCacSRENpF2QbZBfiyN/mlQlY21Qd6PSHef54Rg29FfM5rhWlEuFfg== +ssh.datasource.origin=AQICAHgxTvGwZVD/2MLMvR9/01Wy8IeL4nGHGGgc5XMYIhkQ2gFoX7QJe7YHVfT6QKf90FzWAAAAojCBnwYJKoZIhvcNAQcGoIGRMIGOAgEAMIGIBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHCkPDcljb9kXkBq9AIBEIBbyA8xiSyWUYw6KV4WGF4BrlT/LcAt4JrHB/C038I5EveqJ82NhmKU4HnNSDWMTWZP/g8Es1wd2jcGFihOYWbJ664V78jCoJ7X1sVh6F+FrBo7xx6jByc6sVHFlg== ssh.port=22 ssh.local-port=3306 ## kms -aws.kms.keyId=03703854-534e-40ec-838c-cf65541f88f8 +aws.kms.keyId=5d1c783d-6c8e-4661-a8ed-a12654aac8c3 aws.kms.encryptionAlgorithm=SYMMETRIC_DEFAULT -aws.kms.profile=default ## movie api movie.baseUrl=http://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index eee38e3..568f206 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -2,21 +2,20 @@ spring.datasource.driver-class-name=org.mariadb.jdbc.Driver ## spring datasource spring.datasource.url=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AEZqo9kRIKYisZIm0Z0xpuOAAAAiDCBhQYJKoZIhvcNAQcGoHgwdgIBADBxBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDMoZI5smgDwlG+0kBgIBEIBE1VE7k3neYIx1Pkx6YqUrTf7LEBAxddoUbZVTZxC7HQJpNJKNlwMoaXXyHdPTkBW15dJiCw5jUzuY9CRV7o/vpogIg9Q= -spring.datasource.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGLdMIjOGdWmBOvj9YZ5685AAAAajBoBgkqhkiG9w0BBwagWzBZAgEAMFQGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMDgyL4QtIvG2hM9/5AgEQgCdVSGC6ySfTUn8ZyXKzj2x9eFu+05ACK2sP55fJtoGP0hncckeuVmQ= -spring.datasource.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGVzdO6dwAvzNw42TIj6JZ1AAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMjonbIr69Og3D+ujOAgEQgCatIXU02fuulW5mm6Bn+X9e/FPMq3Fk/GhOLyYpudTHfSkRtMORQQ== +spring.datasource.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AFDjW/RUZSn5wxJhd1XeZOgAAAAYzBhBgkqhkiG9w0BBwagVDBSAgEAME0GCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM0eyz4OI2Vkkc/uTUAgEQgCAXcQUOKDsI26jXFzQfba7Cv2EHO51213zwOT0hzc0SnQ== +spring.datasource.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGiM8q8PoYW89muC9gXzZ5GAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMxGIq7N/hB2XW5NODAgEQgCZeBBgSs9LrXXuCsMAHE53Rsg1WGKfDzjMaAoFRDjEKoGTT9uFThQ== ## ssh -ssh.host=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGU6PSF+1N5z7We+p8jZ0o0AAAAaDBmBgkqhkiG9w0BBwagWTBXAgEAMFIGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM9b3AkdokJsb66zbHAgEQgCXIPbqF5l817FjPbh2LXBzXndYJiSUqyDGZXzyfppZi3qqlSYZV -ssh.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AE68oCmRn5p/q+G/NzwrKpHAAAAYjBgBgkqhkiG9w0BBwagUzBRAgEAMEwGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMCABMThPwxXQHcl5bAgEQgB8+W4uWWbUm8bpo7QTCtjYyJ/ZB5hv4SKstc3Xa4Ltt -ssh.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGlm0DJ1SuCTcwtPCyXZLYjAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM1awvQRELJm1vQL/oAgEQgCasN+qZQ0q7w4FOhwRTCzPYzoMnraA1yx4T9hz2U3j4SGn3+3yEaQ== -ssh.datasource.origin=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGuDWPowi/9gzZpLApcSpl9AAAAmzCBmAYJKoZIhvcNAQcGoIGKMIGHAgEAMIGBBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNqU18Wps+61WNJv6QIBEIBUziHPbeZlunGk/0EvzFYVr2TG4HYuwKd9bQcUdRL7PPkZqO+zdFXeYRtgfhyq/isQqKav0E7LAsnrruFaM0BfGmoqHChECFdSABUJ7+uSdWF2F/vV +ssh.host=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGc1mlusKIbT/FmW65+2rl4AAAAbDBqBgkqhkiG9w0BBwagXTBbAgEAMFYGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMvijHTTZUUuGjjF4jAgEQgCmKq00a3J3yAUkdljTk342nBjYaeLr5BQeJ2kIISM3P/WPMXKQB/0WzlQ== +ssh.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AE26DRJsDEHLdAnA5QIkHfYAAAAZjBkBgkqhkiG9w0BBwagVzBVAgEAMFAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMd4p4zWU1dg+tbLAKAgEQgCNWOoyj2Ual/lQaCe7yz2JkY6TXbUR25FO6SkPkYaAncbgkcA== +ssh.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AFNH5Ud+NVY/FVqOa99UOeMAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMsRopSwW2oDrqOTNrAgEQgCacSRENpF2QbZBfiyN/mlQlY21Qd6PSHef54Rg29FfM5rhWlEuFfg== +ssh.datasource.origin=AQICAHgxTvGwZVD/2MLMvR9/01Wy8IeL4nGHGGgc5XMYIhkQ2gFoX7QJe7YHVfT6QKf90FzWAAAAojCBnwYJKoZIhvcNAQcGoIGRMIGOAgEAMIGIBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHCkPDcljb9kXkBq9AIBEIBbyA8xiSyWUYw6KV4WGF4BrlT/LcAt4JrHB/C038I5EveqJ82NhmKU4HnNSDWMTWZP/g8Es1wd2jcGFihOYWbJ664V78jCoJ7X1sVh6F+FrBo7xx6jByc6sVHFlg== ssh.port=22 ssh.local-port=3306 ## kms -aws.kms.keyId=03703854-534e-40ec-838c-cf65541f88f8 +aws.kms.keyId=5d1c783d-6c8e-4661-a8ed-a12654aac8c3 aws.kms.encryptionAlgorithm=SYMMETRIC_DEFAULT -aws.kms.profile=default ## movie api movie.baseUrl=http://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4b8f1b6..81344c2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,28 +4,41 @@ spring.datasource.driver-class-name=org.mariadb.jdbc.Driver ## spring datasource spring.datasource.url=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AEZqo9kRIKYisZIm0Z0xpuOAAAAiDCBhQYJKoZIhvcNAQcGoHgwdgIBADBxBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDMoZI5smgDwlG+0kBgIBEIBE1VE7k3neYIx1Pkx6YqUrTf7LEBAxddoUbZVTZxC7HQJpNJKNlwMoaXXyHdPTkBW15dJiCw5jUzuY9CRV7o/vpogIg9Q= -spring.datasource.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGLdMIjOGdWmBOvj9YZ5685AAAAajBoBgkqhkiG9w0BBwagWzBZAgEAMFQGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMDgyL4QtIvG2hM9/5AgEQgCdVSGC6ySfTUn8ZyXKzj2x9eFu+05ACK2sP55fJtoGP0hncckeuVmQ= -spring.datasource.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGVzdO6dwAvzNw42TIj6JZ1AAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMjonbIr69Og3D+ujOAgEQgCatIXU02fuulW5mm6Bn+X9e/FPMq3Fk/GhOLyYpudTHfSkRtMORQQ== +spring.datasource.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AFDjW/RUZSn5wxJhd1XeZOgAAAAYzBhBgkqhkiG9w0BBwagVDBSAgEAME0GCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM0eyz4OI2Vkkc/uTUAgEQgCAXcQUOKDsI26jXFzQfba7Cv2EHO51213zwOT0hzc0SnQ== +spring.datasource.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGiM8q8PoYW89muC9gXzZ5GAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMxGIq7N/hB2XW5NODAgEQgCZeBBgSs9LrXXuCsMAHE53Rsg1WGKfDzjMaAoFRDjEKoGTT9uFThQ== ## ssh -ssh.host=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGU6PSF+1N5z7We+p8jZ0o0AAAAaDBmBgkqhkiG9w0BBwagWTBXAgEAMFIGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM9b3AkdokJsb66zbHAgEQgCXIPbqF5l817FjPbh2LXBzXndYJiSUqyDGZXzyfppZi3qqlSYZV -ssh.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AE68oCmRn5p/q+G/NzwrKpHAAAAYjBgBgkqhkiG9w0BBwagUzBRAgEAMEwGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMCABMThPwxXQHcl5bAgEQgB8+W4uWWbUm8bpo7QTCtjYyJ/ZB5hv4SKstc3Xa4Ltt -ssh.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGlm0DJ1SuCTcwtPCyXZLYjAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM1awvQRELJm1vQL/oAgEQgCasN+qZQ0q7w4FOhwRTCzPYzoMnraA1yx4T9hz2U3j4SGn3+3yEaQ== -ssh.datasource.origin=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGuDWPowi/9gzZpLApcSpl9AAAAmzCBmAYJKoZIhvcNAQcGoIGKMIGHAgEAMIGBBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNqU18Wps+61WNJv6QIBEIBUziHPbeZlunGk/0EvzFYVr2TG4HYuwKd9bQcUdRL7PPkZqO+zdFXeYRtgfhyq/isQqKav0E7LAsnrruFaM0BfGmoqHChECFdSABUJ7+uSdWF2F/vV +ssh.host=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGc1mlusKIbT/FmW65+2rl4AAAAbDBqBgkqhkiG9w0BBwagXTBbAgEAMFYGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMvijHTTZUUuGjjF4jAgEQgCmKq00a3J3yAUkdljTk342nBjYaeLr5BQeJ2kIISM3P/WPMXKQB/0WzlQ== +ssh.username=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AE26DRJsDEHLdAnA5QIkHfYAAAAZjBkBgkqhkiG9w0BBwagVzBVAgEAMFAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMd4p4zWU1dg+tbLAKAgEQgCNWOoyj2Ual/lQaCe7yz2JkY6TXbUR25FO6SkPkYaAncbgkcA== +ssh.password=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AFNH5Ud+NVY/FVqOa99UOeMAAAAaTBnBgkqhkiG9w0BBwagWjBYAgEAMFMGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMsRopSwW2oDrqOTNrAgEQgCacSRENpF2QbZBfiyN/mlQlY21Qd6PSHef54Rg29FfM5rhWlEuFfg== +ssh.datasource.origin=AQICAHgxTvGwZVD/2MLMvR9/01Wy8IeL4nGHGGgc5XMYIhkQ2gFoX7QJe7YHVfT6QKf90FzWAAAAojCBnwYJKoZIhvcNAQcGoIGRMIGOAgEAMIGIBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHCkPDcljb9kXkBq9AIBEIBbyA8xiSyWUYw6KV4WGF4BrlT/LcAt4JrHB/C038I5EveqJ82NhmKU4HnNSDWMTWZP/g8Es1wd2jcGFihOYWbJ664V78jCoJ7X1sVh6F+FrBo7xx6jByc6sVHFlg== ssh.port=22 ssh.local-port=3306 ## kms -aws.kms.keyId=03703854-534e-40ec-838c-cf65541f88f8 +aws.kms.keyId=5d1c783d-6c8e-4661-a8ed-a12654aac8c3 aws.kms.encryptionAlgorithm=SYMMETRIC_DEFAULT -aws.kms.profile=default ## movie api movie.baseUrl=http://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json movie.key=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AEdAQDVPqdOhpfNbxFhaX/rAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMnYW/M8ND+IPKIhQ8AgEQgDv37SLjLvGtPBohoi8i1Z+SFBaopteVK3e+36yH3RB44h/Ik0ZyxE69hlJ0T8SHnTLWzLKsk5TKP0uwFA== + +## festival api festival.baseUrl=http://api.data.go.kr/openapi/tn_pubr_public_cltur_fstvl_api festival.key=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AGEXII6Ufa8dIEGD6MQNyM9AAAAujCBtwYJKoZIhvcNAQcGoIGpMIGmAgEAMIGgBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDEphbmyNQTiCTHtF/gIBEIBz8Fj7Ij/xk+O5hXWQfMOsIfc6uAYrvF2Xew+p0qLkX3DO0Plzjt4EMzaUP504RIMr7s+Yhx2y2Lq5SZ9I/cZ4swMYIOwj8FXDLeFy/k3dfwFdnBRh1kmvIDiMSg5kj4kgk05nzHyJ5KloYXXngp/ZQzsdpA== +## camping api +camping.key=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AF5N6HoIJmz3ef5mw7h81sOAAAAujCBtwYJKoZIhvcNAQcGoIGpMIGmAgEAMIGgBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDDvX80CMep9JqAHwVgIBEIBzD41zp/0NHhhvJ4nFvikmteZp501dy09Q/RjJ631ggAcTPaskdGKn6+qwvQVLw3vWesWOSycm3NiMeatVHUwSBZ4AKFGxfhHIsinf1Hyl2RdKjbbeYLYTtTiJY8jh4kZrXUPn6zQ4/vVr75WcIi9eny6lhw== +camping.baseUrl=http://apis.data.go.kr/B551011/GoCamping/basedList + +## movie api tmdb +movie.tmdb.baseUrl=https://api.themoviedb.org/3/discover/movie +movie.tmdb.key=AQICAHg2j0pl3GguoQNkjcreuBPYJqI3OPeLEOelKIkMSzn01AELxjw41yAPSTnmp5zrZNWQAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMHCIOEwhqioIojbKiAgEQgDsmsdMzlgQamoyDnGglRz5A2BNtMvn/4BIVT75wmxUz82NvOoh+Tno236+8lcoAoYdWzfgFVk7cAMNZdw== + +## restaurant api +restaurant.key=AQICAHgxTvGwZVD/2MLMvR9/01Wy8IeL4nGHGGgc5XMYIhkQ2gFwIra357LjWgWTfXdGHOTMAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM0t0Y5XX4gbI1BsAnAgEQgDtbOGiG1iS5qgxCIfnvMf/pEad1kQAOJrPoc39lLxiTk/Vw+bw/DqE/Sdmj/ibi3fakUgdXhdK111insQ== +restaurant.baseUrl=https://dapi.kakao.com/v2/local/search/category + #update the schema with the given values. spring.jpa.hibernate.ddl-auto=update #To beautify or pretty print the SQL diff --git a/src/test/java/com/catcher/batch/BatchApplicationTests.java b/src/test/java/com/catcher/batch/BatchApplicationTests.java index 5360bca..95d3a8b 100644 --- a/src/test/java/com/catcher/batch/BatchApplicationTests.java +++ b/src/test/java/com/catcher/batch/BatchApplicationTests.java @@ -2,8 +2,11 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; @SpringBootTest +@MockBean(JpaMetamodelMappingContext.class) class BatchApplicationTests { @Test diff --git a/src/test/java/com/catcher/batch/core/service/RestaurantServiceTest.java b/src/test/java/com/catcher/batch/core/service/RestaurantServiceTest.java new file mode 100644 index 0000000..81fa8a9 --- /dev/null +++ b/src/test/java/com/catcher/batch/core/service/RestaurantServiceTest.java @@ -0,0 +1,107 @@ +package com.catcher.batch.core.service; + +import com.catcher.batch.core.database.CatcherItemRepository; +import com.catcher.batch.core.database.CategoryRepository; +import com.catcher.batch.core.database.LocationRepository; +import com.catcher.batch.core.domain.entity.CatcherItem; +import com.catcher.batch.core.domain.entity.Category; +import com.catcher.batch.core.domain.entity.Location; +import com.catcher.batch.core.dto.RestaurantApiResponse; +import org.apache.commons.codec.digest.DigestUtils; +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@MockitoSettings(strictness = Strictness.LENIENT) +@ExtendWith(MockitoExtension.class) +class RestaurantServiceTest { + + @Mock + private CatcherItemRepository catcherItemRepository; + + @Mock + private LocationRepository locationRepository; + + @Mock + private CategoryRepository categoryRepository; + + @InjectMocks + private RestaurantService restaurantService; + + private RestaurantApiResponse restaurantApiResponse; + private RestaurantApiResponse.RestaurantItem restaurantItem; + private CatcherItem catcherItem; + private Location location; + private Category category; + + @BeforeEach + void beforeEach() { + restaurantApiResponse = Mockito.mock(RestaurantApiResponse.class); + restaurantItem = Mockito.mock(RestaurantApiResponse.RestaurantItem.class); + catcherItem = Mockito.mock(CatcherItem.class); + location = Mockito.mock(Location.class); + category = Mockito.mock(Category.class); + } + + @DisplayName("SUCCESS : 음식점 db 저장 성공 테스트") + @Test + void batchTest_SuccessfulSave() { + // Given + setUpRestaurantItem(); + + // When + restaurantService.batch(restaurantApiResponse); + + // Then + Mockito.verify(catcherItemRepository, Mockito.times(1)).saveAll(Mockito.anyList()); + } + + @DisplayName("SUCCESS: 음식점 db 저장 성공 테스트 - 중복 객체 확인") + @Test + void batchTest_SuccessfulSaveWithDuplicate() { + // Given + String hashKey = DigestUtils.sha256Hex("restaurant" + "-" + "key"); + + when(catcherItem.getItemHashValue()).thenReturn(hashKey); + when(catcherItem.getLocation()).thenReturn(location); + when(catcherItem.getTitle()).thenReturn("저장된 맛집"); + when(catcherItem.getCategory()).thenReturn(category); + + when(catcherItemRepository.findByCategory(any(Category.class))).thenAnswer(invocation -> Collections.singletonList(catcherItem)); + + setUpRestaurantItem(); + + // When + restaurantService.batch(restaurantApiResponse); + + // Then + Mockito.verify(catcherItemRepository, Mockito.never()).saveAll(Mockito.anyList()); + } + + private void setUpRestaurantItem() { + when(restaurantItem.getKey()).thenReturn("key"); + when(restaurantItem.getName()).thenReturn("맛집"); + when(restaurantItem.getResourceUrl()).thenReturn("url"); + when(restaurantItem.getAddress()).thenReturn("서울 관악구"); + when(restaurantItem.getLatitude()).thenReturn("37.4783683761333"); + when(restaurantItem.getLongitude()).thenReturn("126.951561853868"); + + Mockito.when(restaurantApiResponse.getItems()).thenReturn(List.of(restaurantItem)); + Mockito.when(categoryRepository.findByName("restaurant")).thenReturn(Optional.of(category)); + Mockito.when(locationRepository.findByDescription("서울", "관악구")).thenReturn(Optional.of(location)); + } +} \ No newline at end of file diff --git a/src/test/java/com/catcher/batch/resource/CampingControllerTest.java b/src/test/java/com/catcher/batch/resource/CampingControllerTest.java new file mode 100644 index 0000000..7117165 --- /dev/null +++ b/src/test/java/com/catcher/batch/resource/CampingControllerTest.java @@ -0,0 +1,59 @@ +package com.catcher.batch.resource; + +import com.catcher.batch.common.service.CatcherFeignService; +import com.catcher.batch.core.domain.CommandExecutor; +import com.catcher.batch.core.dto.CampingApiResponse; +import com.catcher.batch.core.service.CampingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.HashMap; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest( + value = CampingController.class, + excludeAutoConfiguration = {SecurityAutoConfiguration.class}) +@MockBean(JpaMetamodelMappingContext.class) +@AutoConfigureMockMvc +class CampingControllerTest { + + @MockBean + private CatcherFeignService catcherFeignService; + + @MockBean + private CommandExecutor commandExecutor; + + @MockBean + private CampingService campingService; + + + @Autowired + private MockMvc mockMvc; + + @DisplayName("SUCCESS : 캠핑 api 응답 200 테스트") + @Test + void getCampingDataByFeign() throws Exception { + CampingApiResponse campingApiResponse = new CampingApiResponse(); + + when(catcherFeignService.parseService(new HashMap<>(), CampingApiResponse.class)) + .thenReturn(campingApiResponse); + + mockMvc.perform(MockMvcRequestBuilders + .get("/camping/feign-batch") + .param("page", "1") + .param("count", "5") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } +} diff --git a/src/test/java/com/catcher/batch/resource/HealthCheckControllerTest.java b/src/test/java/com/catcher/batch/resource/HealthCheckControllerTest.java index 597e375..207aef3 100644 --- a/src/test/java/com/catcher/batch/resource/HealthCheckControllerTest.java +++ b/src/test/java/com/catcher/batch/resource/HealthCheckControllerTest.java @@ -5,6 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; @@ -16,6 +18,7 @@ value = HealthCheckController.class, excludeAutoConfiguration = {SecurityAutoConfiguration.class} ) +@MockBean(JpaMetamodelMappingContext.class) class HealthCheckControllerTest { @Autowired