From f33bbc5f38b543b506bb2b5f648bf4da73c4c34a Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Sat, 13 Dec 2025 15:49:11 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor=20:=20=EC=99=B8=EB=B6=80=20API=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=20=EB=B0=8F=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EB=A1=9C=EC=A7=81=20=EA=B0=80=EC=8B=9C?= =?UTF-8?q?=EC=84=B1=20=EC=A6=9D=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../concerts/service/KopisApiService.java | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java index 95edcc8d..0911cc88 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java @@ -104,23 +104,7 @@ public ConcertListResponse setConcertsList() throws InterruptedException { } //콘서트 최고 금액, 최저 금액 처리. - String price = concertDetail.getConcertPrice(); - String[] bits = price.split(" "); - int maxPrice = 0; - int minPrice = Integer.MAX_VALUE; - if (bits.length == 1) { - minPrice = 0; - } else { - for (String bit : bits) { - bit = bit.replaceAll(",", ""); - if (bit.endsWith("원")) { - bit = bit.replaceAll("원", ""); - maxPrice = Math.max(Integer.parseInt(bit), maxPrice); - minPrice = Math.min(Integer.parseInt(bit), minPrice); - } - } - } - + TicketPrice ticketPrice = new TicketPrice(concertDetail.getConcertPrice()); // 콘서트 저장 Concert concert = new Concert( @@ -130,8 +114,8 @@ public ConcertListResponse setConcertsList() throws InterruptedException { dateStringToDateTime(concertDetail.getStartDate()), dateStringToDateTime(concertDetail.getEndDate()), "", - maxPrice, - minPrice, + ticketPrice.maxPrice, + ticketPrice.minPrice, concertDetail.getPosterUrl(), concertDetail.getApiConcertId() ); @@ -149,7 +133,6 @@ public ConcertListResponse setConcertsList() throws InterruptedException { ticketOfficeRepository.save(to); } log.info("Concert saved: " + savedConcert); - Thread.sleep(300); } log.info(totalConcertsList.size() + "개의 공연 데이터 저장 완료!"); @@ -198,22 +181,7 @@ public void updateConcertData() throws InterruptedException { // 1주일 단위 } //콘서트 최고 금액, 최저 금액 처리. - String price = concertDetail.getConcertPrice(); - String[] bits = price.split(" "); - int maxPrice = 0; - int minPrice = Integer.MAX_VALUE; - if (bits.length == 1) { - minPrice = 0; - } else { - for (String bit : bits) { - bit = bit.replaceAll(",", ""); - if (bit.endsWith("원")) { - bit = bit.replaceAll("원", ""); - maxPrice = Math.max(Integer.parseInt(bit), maxPrice); - minPrice = Math.min(Integer.parseInt(bit), minPrice); - } - } - } + TicketPrice ticketPrice = new TicketPrice(concertDetail.getConcertPrice()); // 콘서트 저장 Concert concert = concertRepository.getConcertByApiConcertId(concertDetail.getApiConcertId()); @@ -225,8 +193,8 @@ public void updateConcertData() throws InterruptedException { // 1주일 단위 dateStringToDateTime(concertDetail.getStartDate()), dateStringToDateTime(concertDetail.getEndDate()), "", - maxPrice, - minPrice, + ticketPrice.maxPrice, + ticketPrice.minPrice, concertDetail.getPosterUrl(), concertDetail.getApiConcertId() ); @@ -235,8 +203,8 @@ public void updateConcertData() throws InterruptedException { // 1주일 단위 concertPlace, concertDetail.getConcertDescription(), "", - maxPrice, - minPrice + ticketPrice.maxPrice, + ticketPrice.minPrice ); } @@ -313,6 +281,8 @@ private ConcertListResponse getConcertListResponse(String serviceKey, LocalDate return performanceListResponse; } + + //콘서트 목록을 조회합니다. private ConcertListResponse getConcertListResponse(String serviceKey, LocalDate sdate, LocalDate edate, int page, LocalDate afterDate) { ConcertListResponse performanceListResponse; performanceListResponse = restClient.get() @@ -332,6 +302,7 @@ private ConcertListResponse getConcertListResponse(String serviceKey, LocalDate } + //콘서트 상세를 조회합니다. private ConcertDetailResponse getConcertDetailResponse(String serviceKey, String concertApiId) { ConcertDetailResponse concertDetailResponse; concertDetailResponse = restClient.get() @@ -345,6 +316,7 @@ private ConcertDetailResponse getConcertDetailResponse(String serviceKey, String } + // 콘서트 장소를 조회합니다. private ConcertPlaceListResponse getConcertPlaceListResponse(String serviceKey, int page) { return restClient.get() .uri(uriBuilder -> @@ -356,6 +328,7 @@ private ConcertPlaceListResponse getConcertPlaceListResponse(String serviceKey, ).retrieve().body(ConcertPlaceListResponse.class); } + //콘서트 장소 상세를 조회합니다. private ConcertPlaceDetailResponse getConcertPlaceDetailResponse(String serviceKey, String concertPlaceId) { return restClient.get() .uri(uriBuilder -> @@ -365,11 +338,38 @@ private ConcertPlaceDetailResponse getConcertPlaceDetailResponse(String serviceK ).retrieve().body(ConcertPlaceDetailResponse.class); } + // API에서 출력하는 날짜 문자열을 LocalDate 객체로 변환 private LocalDate dateStringToDateTime(String dateString) { + // "yyyy.MM.dd" 형식으로 들어옴 String [] split = dateString.split("\\."); int year = Integer.parseInt(split[0]); int month = Integer.parseInt(split[1]); int day = Integer.parseInt(split[2]); return LocalDate.of(year, month, day); } + + // 가격을 저장할 내부 객체 + private class TicketPrice{ + int maxPrice; + int minPrice; + + // 문자열 가격 정보를 받아서 변환 후 저장. + public TicketPrice(String price) { + String[] bits = price.split(" "); + int maxPrice = 0; + int minPrice = Integer.MAX_VALUE; + if (bits.length == 1) { + minPrice = 0; + } else { + for (String bit : bits) { + bit = bit.replaceAll(",", ""); + if (bit.endsWith("원")) { + bit = bit.replaceAll("원", ""); + this.maxPrice = Math.max(Integer.parseInt(bit), maxPrice); + this.minPrice = Math.min(Integer.parseInt(bit), minPrice); + } + } + } + } + } } From a0edcb57e52d49913e3835d0daedf5e5b1a572af Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Sun, 14 Dec 2025 13:35:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat=20:=20Kopis=20api=20=EA=B3=B5=EC=97=B0?= =?UTF-8?q?=20=EC=9E=A5=EC=86=8C=EB=AA=85=20=EA=B8=B0=EC=A4=80=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/concerts/service/KopisApiService.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java index 0911cc88..9f1c6115 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/KopisApiService.java @@ -139,6 +139,7 @@ public ConcertListResponse setConcertsList() throws InterruptedException { return plr; } + // 매주 월요일 새벽 2시 기준으로 데이터 갱신 @Scheduled(cron = "0 0 2 * * Mon") public void updateConcertData() throws InterruptedException { // 1주일 단위로 추가된 공연을 더하기 LocalDate weekBefore = LocalDate.now().minusDays(8); @@ -316,7 +317,7 @@ private ConcertDetailResponse getConcertDetailResponse(String serviceKey, String } - // 콘서트 장소를 조회합니다. + // 콘서트 장소 목록을 조회합니다. private ConcertPlaceListResponse getConcertPlaceListResponse(String serviceKey, int page) { return restClient.get() .uri(uriBuilder -> @@ -328,6 +329,19 @@ private ConcertPlaceListResponse getConcertPlaceListResponse(String serviceKey, ).retrieve().body(ConcertPlaceListResponse.class); } + // 콘서트 장소 목록을 이름 기준으로 조회합니다. + private ConcertPlaceListResponse getConcertPlaceListResponseByName(String serviceKey, String name, int page){ + return restClient.get() + .uri(uriBuilder -> + uriBuilder.path("/prfplc") + .queryParam("service", serviceKey) + .queryParam("cpage", page) + .queryParam("rows", 100) + .queryParam("shprfnmfct", name) + .build() + ).retrieve().body(ConcertPlaceListResponse.class); + } + //콘서트 장소 상세를 조회합니다. private ConcertPlaceDetailResponse getConcertPlaceDetailResponse(String serviceKey, String concertPlaceId) { return restClient.get() From 602ff039c814bf2266070153b6f97654395f6b00 Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Mon, 15 Dec 2025 16:33:17 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat=20:=20=ED=81=AC=EB=A1=A4=EB=A7=81=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=8F=84=EC=A4=91=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=B0=A9=ED=96=A5=EC=84=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 ++ .../concerts/service/ConcertService.java | 3 +- .../concerts/service/JsoupApiService.java | 45 +++++++++++++++++++ .../concerts/service/interparkApiService.java | 37 +++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/JsoupApiService.java create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/interparkApiService.java diff --git a/build.gradle.kts b/build.gradle.kts index 57642f05..cbe50bc7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,6 +69,9 @@ dependencies { // XML 파싱 implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml") + + // 정적 HTML 문서 기준 크롤링 + implementation("org.jsoup:jsoup:1.21.2") } tasks.withType { diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java index 7ac6f001..b740f304 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java @@ -39,7 +39,7 @@ public class ConcertService { private final TicketOfficeRepository ticketOfficeRepository; - private final UserRepository userRepository; + private final JsoupApiService jsoupApiService; public List getConcertsList(Pageable pageable) { return concertRepository.getConcertItems(pageable); @@ -68,6 +68,7 @@ public List getConcertsList2(Pageable pageable) { return concertItems; } */ + public List getTicketOfficesList(long concertId) { Concert concert = new Concert(concertId); List ticketOffices = ticketOfficeRepository.getTicketOfficesByConcert(concert); diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/JsoupApiService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/JsoupApiService.java new file mode 100644 index 00000000..fbd18aea --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/JsoupApiService.java @@ -0,0 +1,45 @@ +package com.back.web7_9_codecrete_be.domain.concerts.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Slf4j +@Service +public class JsoupApiService { + + public String getHtmlByUrl(String url) { + StringBuilder sb = new StringBuilder(); + String res = ""; + try { + Document doc = Jsoup.connect(url).timeout(10*1000).get(); + Element nextData = doc.selectFirst("script#__NEXT_DATA__"); + log.info(doc.toString()); + String json = nextData.html(); + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(json); + String timestamp = + root.path("props") + .path("pageProps") + .path("dehydratedState") + .path("queries") + .get(0) + .path("state") + .path("data") + .path("availableBeginTimestamp") + .asText(); + log.info("timestamp: {}", timestamp); + sb.append(timestamp); + } catch (IOException e){ + log.error(e.getMessage()); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/interparkApiService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/interparkApiService.java new file mode 100644 index 00000000..644bb0a9 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/interparkApiService.java @@ -0,0 +1,37 @@ +package com.back.web7_9_codecrete_be.domain.concerts.service; + +import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertPlaceRepository; +import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertRepository; +import com.back.web7_9_codecrete_be.domain.concerts.repository.TicketOfficeRepository; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +import java.time.LocalDateTime; + +@Service +public class interparkApiService { + private final RestClient restClient; + + public interparkApiService(ConcertRepository concertRepository, ConcertPlaceRepository placeRepository, TicketOfficeRepository ticketOfficeRepository) { + this.restClient = RestClient.builder() + .baseUrl("https://api-ticketfront.interpark.com/v1/goods/") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + } + + public LocalDateTime getTicketOpenDate(String goodsId){ + restClient.get() + .uri(uriBuilder -> + uriBuilder.path("/"+goodsId) + .path("/summary") + .queryParam("goodsCode",goodsId) + .build() + ).retrieve(); + + return null; + } + + +}