From 25244703e69b32952c8d4cda5ddd7b2981fbc9b9 Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Tue, 16 Dec 2025 10:37:16 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConcertAdminController.java | 34 +++++++++++++++---- .../controller/ConcertController.java | 25 ++++++++++++-- .../concerts/dto/concert/ConcertItem.java | 2 +- .../dto/concert/ConcertLikeResponse.java | 4 +++ .../concert/ConcertTicketTimeSetRequest.java | 4 +++ .../dto/concert/ConcertUpdateRequest.java | 18 ++++++++++ 6 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java index ba55b07a..ce00d95e 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java @@ -9,6 +9,8 @@ import com.back.web7_9_codecrete_be.domain.concerts.service.ConcertService; import com.back.web7_9_codecrete_be.domain.concerts.service.KopisApiService; import com.back.web7_9_codecrete_be.global.rsData.RsData; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -25,43 +27,63 @@ public class ConcertAdminController { // todo : 인증 권한 추가하기 private final ConcertService concertService; private final KopisApiService kopisApiService; - @GetMapping("totalGetTest") - public ConcertListResponse totalGetTest() throws InterruptedException { - return kopisApiService.setConcertsList(); - } - - @GetMapping("setConcertPlace") + @Operation(summary = "초기 공연 정보 저장", description = "25년 12월부터 앞으로 6개월 이후까지의 전체 공연의 정보를 가져와서 저장합니다. 대략 10~12분 정도 시간이 소요됩니다.") + @PostMapping("setConcertPlace") public ConcertPlaceListResponse setConcertPlace() throws InterruptedException { return kopisApiService.setConcertPlace(); } + @Operation(summary = "공연 정보 갱신",description = "공연 정보를 직접 갱신합니다.") @PatchMapping("updateConcert/{concertId}") public RsData updateConcert( + @Schema(description = "갱신 대상이 될 공연의 ID 값입니다.") @PathVariable Long concertId, + @Schema(description = "갱신 내용입니다.") @RequestBody ConcertUpdateRequest concertUpdateRequest ){ ConcertItem concertItem = concertService.updateConcert(concertId, concertUpdateRequest); return RsData.success("공연 정보 수정이 완료되었습니다.",concertItem); } + @Operation(summary = "예매 시간이 없는 공연 목록 조회", description = "예매 시간이 없는 공연들을 공연시간 내림차순으로 출력합니다.") @GetMapping("noTicketTimeList") public List getNoTicketTimeConcertsList( + @Schema(description = "무한스크롤 및 페이징 처리에 사용할 Pageable입니다.") Pageable pageable ) { return concertService.getNoTicketTimeConcertsList(pageable); } + @Operation(summary = "공연을 삭제합니다.", description = "해당 공연을 삭제합니다.") @DeleteMapping("deleteConcert/{concertId}") public RsData deleteConcert(@PathVariable Long concertId){ concertService.deleteConcert(concertId); return RsData.success("공연 정보 삭제에 성공하였습니다.",null); } + @Operation(summary = "예매 시간 등록",description = "개별 공연에 대한 예매 시간을 설정합니다.") @PatchMapping("ticketTimeSet") public RsData ticketTimeSet( @RequestBody ConcertTicketTimeSetRequest concertTicketTimeSetRequest ){ return RsData.success(concertService.setConcertTime(concertTicketTimeSetRequest)); } + + // todo: 내용 구현 필요 + @Operation(summary = "개별 공연 API통한 갱신",description = "개별 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고 해당 데이터를 갱신합니다.") + @PatchMapping("updateConcertByKopisAPI/{concertId}") + public RsData updateConcertByKopisAPI( + @Schema(description = "갱신 대상이 될 공연의 ID 값입니다.") + @PathVariable Long ConcertId + ){ + return null; + } + + // todo: 내용 구현 필요, 결과 DTO 따로 만들기. + @Operation(summary = "공연 목록 갱신", description = "전체 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고, 바뀐 내용을 갱신하고 추가된 공연을 가져옵니다.") + @PostMapping("updateConcert") + public RsData updateConcert(){ + return null; + } } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java index 71934c63..df3d4190 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; +import javax.swing.*; import java.util.List; @RestController @@ -52,6 +53,16 @@ public RsData> getUpComingList ( return RsData.success(concertService.getUpcomingConcertsList(pageable)); } + // todo: 내용 구현 필요 + @Operation(summary = "공연 예매일 기준 조회", description = "현 시간을 기준으로 예매시간을 내림차순으로 출력하는 공연 목록을 조회합니다.") + @GetMapping("upComingTicketingList") + public RsData> getUpComingTicketingList ( + @Schema(description = "페이징 처리 또는 무한 스크롤 구현에 사용할 Pageable 객체입니다.") + Pageable pageable + ){ + return null; + } + @Operation(summary = "좋아요 한 공연 조회", description = "좋아요를 누른 공연에 대한 목록을 조회합니다. 저장 날짜를 기준으로 내림차순 정렬로 표시합니다.(최신으로 추가된 목록순입니다.)") @GetMapping("likedConcertList") public RsData> getLikedConcertList ( @@ -72,8 +83,6 @@ public ConcertDetailResponse getConcertDetail( return concertService.getConcertDetail(concertId); } - - @Operation(summary = "공연 예매처 조회", description = "공연에 대한 예매처들을 조회합니다.") @GetMapping("ticketOffices") public RsData> getTicketOffices ( @@ -113,5 +122,17 @@ public RsData isLikeConcert( return RsData.success(concertService.isLikeConcert(concertId, user)); } + // todo : 내용 구현 필요 + @Operation(summary = "공연 검색", description = "공연 정보를 검색합니다.") + @GetMapping("search") + public RsData> searchConcert( + @Schema(description = "공연 정보 검색 키워드입니다.") + @RequestParam String keyword + ){ + return null; + } + + + } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertItem.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertItem.java index a93bd3ca..79856417 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertItem.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertItem.java @@ -21,7 +21,7 @@ public class ConcertItem { @Schema(description = "콘서트 장소 이름입니다.") private String placeName; - @Schema(description = "콘서트 예매 시작 날짜입니다.") + @Schema(description = "콘서트 예매 시작 날짜입니다.",format = "yyyy-MM-ddThh:mm:ss") private LocalDateTime ticketTime; @Schema(description = "콘서트 시작 날짜입니다.",format = "yyyy-MM-dd") diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertLikeResponse.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertLikeResponse.java index 919e6f5d..7e7b0e96 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertLikeResponse.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertLikeResponse.java @@ -2,11 +2,15 @@ import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertLike; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter public class ConcertLikeResponse { + @Schema(description = "공연 ID 입니다.") private Long concertId; + + @Schema(description = "공연 좋아요 여부입니다.") private Boolean isLike; public ConcertLikeResponse(Concert concert, Boolean isLike) { diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertTicketTimeSetRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertTicketTimeSetRequest.java index b37178ab..73d2e7fd 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertTicketTimeSetRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertTicketTimeSetRequest.java @@ -1,11 +1,15 @@ package com.back.web7_9_codecrete_be.domain.concerts.dto.concert; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import java.time.LocalDateTime; @Getter public class ConcertTicketTimeSetRequest { + @Schema(description = "공연 ID 입니다.") private Long concertId; + + @Schema(description = "티켓팅 시간입니다.") private LocalDateTime ticketTime; } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertUpdateRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertUpdateRequest.java index 69d61b9b..3f016745 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertUpdateRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertUpdateRequest.java @@ -1,18 +1,36 @@ package com.back.web7_9_codecrete_be.domain.concerts.dto.concert; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import java.time.LocalDate; @Getter public class ConcertUpdateRequest { + @Schema(description = "수정할 대상이 될 공연 ID 입니다.") private Long concertId; + + @Schema(description = "공연 이름입니다.") private String name; + + @Schema(description = "공연 설명입니다.") private String description; + + @Schema(description = "공연장 ID 입니다.") private Long placeId; + + @Schema(description = "공연 시작 날짜입니다.") private LocalDate StartDate; + + @Schema(description = "공연 종료 날짜입니다.") private LocalDate EndDate; + + @Schema(description = "공연 포스터 URL 입니다.") private String posterUrl; + + @Schema(description = "공연 티켓 최고가입니다.") private int maxPrice; + + @Schema(description = "공연 티켓 최저가입니다.") private int minPrice; } From 36569ebcc3262de5896a240604654154980435fb Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Tue, 16 Dec 2025 10:47:13 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat=20:=20=EA=B5=AC=ED=98=84=20=EC=A0=84?= =?UTF-8?q?=EC=9D=B8=20API=EB=8A=94=20summary=EC=97=90=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=ED=95=A8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/concerts/controller/ConcertAdminController.java | 4 ++-- .../domain/concerts/controller/ConcertController.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java index ce00d95e..f02f2a62 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java @@ -71,7 +71,7 @@ public RsData ticketTimeSet( } // todo: 내용 구현 필요 - @Operation(summary = "개별 공연 API통한 갱신",description = "개별 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고 해당 데이터를 갱신합니다.") + @Operation(summary = "개별 공연 API통한 갱신(구현 전)",description = "개별 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고 해당 데이터를 갱신합니다.") @PatchMapping("updateConcertByKopisAPI/{concertId}") public RsData updateConcertByKopisAPI( @Schema(description = "갱신 대상이 될 공연의 ID 값입니다.") @@ -81,7 +81,7 @@ public RsData updateConcertByKopisAPI( } // todo: 내용 구현 필요, 결과 DTO 따로 만들기. - @Operation(summary = "공연 목록 갱신", description = "전체 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고, 바뀐 내용을 갱신하고 추가된 공연을 가져옵니다.") + @Operation(summary = "공연 목록 갱신(구현 전)", description = "전체 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고, 바뀐 내용을 갱신하고 추가된 공연을 가져옵니다.") @PostMapping("updateConcert") public RsData updateConcert(){ return null; diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java index df3d4190..b366e1a4 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java @@ -54,7 +54,7 @@ public RsData> getUpComingList ( } // todo: 내용 구현 필요 - @Operation(summary = "공연 예매일 기준 조회", description = "현 시간을 기준으로 예매시간을 내림차순으로 출력하는 공연 목록을 조회합니다.") + @Operation(summary = "공연 예매일 기준 조회(구현 전)", description = "현 시간을 기준으로 예매시간을 내림차순으로 출력하는 공연 목록을 조회합니다.") @GetMapping("upComingTicketingList") public RsData> getUpComingTicketingList ( @Schema(description = "페이징 처리 또는 무한 스크롤 구현에 사용할 Pageable 객체입니다.") @@ -123,7 +123,7 @@ public RsData isLikeConcert( } // todo : 내용 구현 필요 - @Operation(summary = "공연 검색", description = "공연 정보를 검색합니다.") + @Operation(summary = "공연 검색(구현 전)", description = "공연 정보를 검색합니다.") @GetMapping("search") public RsData> searchConcert( @Schema(description = "공연 정보 검색 키워드입니다.") From 039b230dc88e2e59ebb075c8111140c714660e60 Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Tue, 16 Dec 2025 12:31:08 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EA=B3=B5=EC=97=B0=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConcertController.java | 16 ++++++----- .../repository/ConcertRepository.java | 27 +++++++++++++++++++ .../concerts/service/ConcertService.java | 8 ++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java index b366e1a4..66ef0bc2 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java @@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springdoc.core.converters.models.PageableAsQueryParam; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -27,6 +28,7 @@ import javax.swing.*; import java.util.List; +@Slf4j @RestController @RequestMapping("api/v1/concerts/") @RequiredArgsConstructor @@ -122,17 +124,17 @@ public RsData isLikeConcert( return RsData.success(concertService.isLikeConcert(concertId, user)); } - // todo : 내용 구현 필요 - @Operation(summary = "공연 검색(구현 전)", description = "공연 정보를 검색합니다.") + // todo : 제목으로 만 검색 기능 구현 -> 추후 아티스트 정보랑 연동 <- 중요 / 정렬 기준? 최신등록순 정렬 + @Operation(summary = "공연 검색", description = "공연 정보를 검색합니다.") @GetMapping("search") public RsData> searchConcert( @Schema(description = "공연 정보 검색 키워드입니다.") - @RequestParam String keyword + @RequestParam String keyword, + @Schema(description = "페이징 처리 또는 무한 스크롤 구현에 쓸 Pageable 객체입니다.") + Pageable pageable + ){ - return null; + return RsData.success(concertService.getConcertListByKeyword(keyword,pageable)); } - - - } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java index 94c07379..9401e0a4 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java @@ -124,6 +124,33 @@ List getNoTicketTimeConcertList( ) List getLikedConcertsList(Pageable pageable, @Param("userId") Long userId); + @Query(""" + SELECT + new com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem( + c.concertId as id, + c.name as name, + c.concertPlace.placeName as placeName, + c.ticketTime as ticketTime, + c.startDate as startDate, + c.endDate as endDate, + c.posterUrl as posterUrl, + c.maxPrice as maxPrice, + c.minPrice as minPrice, + c.viewCount as viewCount, + c.likeCount as likeCount + ) + FROM + Concert c + WHERE + c.name LIKE %:keyword% + ORDER BY + c.concertId + DESC +""") + List getConcertItemsByKeyword( + @Param("keyword") + String keyword, + Pageable pageable); @Query(""" SELECT 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 d3417480..f7d6c7bb 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 @@ -49,6 +49,14 @@ public List getNoTicketTimeConcertsList(Pageable pageable) { return concertRepository.getNoTicketTimeConcertList(pageable); } + public List getConcertListByKeyword(String keyword, Pageable pageable) { + if(keyword == null || keyword.isEmpty()){ + + } + + return concertRepository.getConcertItemsByKeyword(keyword, pageable); + } + public ConcertDetailResponse getConcertDetail(long concertId) { ConcertDetailResponse concertDetailResponse = concertRepository.getConcertDetailById(concertId); List concertImages = concertImageRepository.getConcertImagesByConcert_ConcertId(concertId); From 9fe97c0bcc810e56193648e1a652b07b59fc04c8 Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Tue, 16 Dec 2025 12:36:38 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix=20:=20swagger=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/concerts/controller/ConcertController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java index 66ef0bc2..ef185836 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java @@ -125,7 +125,7 @@ public RsData isLikeConcert( } // todo : 제목으로 만 검색 기능 구현 -> 추후 아티스트 정보랑 연동 <- 중요 / 정렬 기준? 최신등록순 정렬 - @Operation(summary = "공연 검색", description = "공연 정보를 검색합니다.") + @Operation(summary = "공연 검색", description = "제목에 키워드를 포함하고 있는 공연 정보를 검색합니다.") @GetMapping("search") public RsData> searchConcert( @Schema(description = "공연 정보 검색 키워드입니다.") From 4e9a04bea1e2e6d09f25c997a827616c517db37e Mon Sep 17 00:00:00 2001 From: Creamcheesepie Date: Tue, 16 Dec 2025 15:37:20 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EA=B3=B5=EC=97=B0=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B0=B1=EC=8B=A0=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81,=20=EA=B0=9C=EB=B3=84=20=EA=B3=B5?= =?UTF-8?q?=EC=97=B0=20=EC=A0=95=EB=B3=B4=20=EA=B0=B1=EC=8B=A0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConcertAdminController.java | 24 +- .../KopisApiDto/result/SetResultResponse.java | 42 ++++ .../domain/concerts/entity/Concert.java | 6 +- .../concerts/entity/ConcertUpdateTime.java | 31 +++ .../repository/ConcertImageRepository.java | 18 ++ .../ConcertUpdateTimeRepository.java | 11 + .../repository/TicketOfficeRepository.java | 17 ++ .../concerts/service/KopisApiService.java | 229 ++++++++++++------ 8 files changed, 289 insertions(+), 89 deletions(-) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/KopisApiDto/result/SetResultResponse.java create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertUpdateTime.java create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertUpdateTimeRepository.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java index f02f2a62..8874a2c3 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java @@ -2,6 +2,7 @@ import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concert.ConcertListResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceListResponse; +import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.result.SetResultResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertDetailResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem; import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertTicketTimeSetRequest; @@ -29,9 +30,9 @@ public class ConcertAdminController { // todo : 인증 권한 추가하기 @Operation(summary = "초기 공연 정보 저장", description = "25년 12월부터 앞으로 6개월 이후까지의 전체 공연의 정보를 가져와서 저장합니다. 대략 10~12분 정도 시간이 소요됩니다.") - @PostMapping("setConcertPlace") - public ConcertPlaceListResponse setConcertPlace() throws InterruptedException { - return kopisApiService.setConcertPlace(); + @PostMapping("setConcertData") + public RsData setConcert() throws InterruptedException { + return RsData.success(kopisApiService.setConcertsList()); } @Operation(summary = "공연 정보 갱신",description = "공연 정보를 직접 갱신합니다.") @@ -70,20 +71,19 @@ public RsData ticketTimeSet( return RsData.success(concertService.setConcertTime(concertTicketTimeSetRequest)); } - // todo: 내용 구현 필요 - @Operation(summary = "개별 공연 API통한 갱신(구현 전)",description = "개별 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고 해당 데이터를 갱신합니다.") + @Operation(summary = "개별 공연 API통한 갱신",description = "개별 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고 해당 데이터를 갱신합니다.") @PatchMapping("updateConcertByKopisAPI/{concertId}") public RsData updateConcertByKopisAPI( @Schema(description = "갱신 대상이 될 공연의 ID 값입니다.") - @PathVariable Long ConcertId + @PathVariable Long concertId ){ - return null; + kopisApiService.concertUpdateByKopisApi(concertId); + return RsData.success(concertService.getConcertDetail(concertId)); } - // todo: 내용 구현 필요, 결과 DTO 따로 만들기. - @Operation(summary = "공연 목록 갱신(구현 전)", description = "전체 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고, 바뀐 내용을 갱신하고 추가된 공연을 가져옵니다.") - @PostMapping("updateConcert") - public RsData updateConcert(){ - return null; + @Operation(summary = "공연 목록 갱신", description = "전체 공연에 대해서 공연 예술 통합망(Kopis)을 통해 데이터를 조회하고, 바뀐 내용을 갱신하고 추가된 공연을 가져옵니다.") + @PostMapping("updateConcertData") + public RsData updateConcert() throws InterruptedException { + return RsData.success(kopisApiService.updateConcertData()); } } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/KopisApiDto/result/SetResultResponse.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/KopisApiDto/result/SetResultResponse.java new file mode 100644 index 00000000..1f65be51 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/KopisApiDto/result/SetResultResponse.java @@ -0,0 +1,42 @@ +package com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class SetResultResponse { + @Schema(description = "추가된 공연 수") + private int addedConcerts; + + @Schema(description = "갱신된 공연 수") + private int updatedConcerts; + + @Schema(description = "추가된 공연 장소 수") + private int addedConcertPlaces; + + @Schema(description = "갱신된 공연 장소 수") + private int updatedConcertPlaces; + + @Schema(description = "추가된 공연 이미지 수") + private int addedConcertImages; + + @Schema(description = "갱신된 공연 이미지 수") + private int updatedConcertImages; + + @Schema(description = "추가된 예매처 사이트 수") + private int addedTicketOffice; + + @Schema(description = "갱신된 예매처 사이트 수") + private int updatedTicketOffice; + + public SetResultResponse(int addedConcerts, int updatedConcerts, int addedConcertPlaces, int updatedConcertPlaces, int addedConcertImages, int updatedConcertImages, int addedTicketOffice, int updatedTicketOffice) { + this.addedConcerts = addedConcerts; + this.updatedConcerts = updatedConcerts; + this.addedConcertPlaces = addedConcertPlaces; + this.updatedConcertPlaces = updatedConcertPlaces; + this.addedConcertImages = addedConcertImages; + this.updatedConcertImages = updatedConcertImages; + this.addedTicketOffice = addedTicketOffice; + this.updatedTicketOffice = updatedTicketOffice; + } +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java index d8644020..beaefc56 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java @@ -83,13 +83,15 @@ public Concert(Long concertId) { this.concertId = concertId; } - public Concert update(ConcertPlace concertPlace, String content, LocalDateTime ticketTime, int maxPrice, int minPrice){ + public Concert updateByAPI(ConcertPlace concertPlace, String content,LocalDate startDate,LocalDate endDate, int maxPrice, int minPrice, String posterUrl) { this.concertPlace = concertPlace; this.content = content; - this.ticketTime = ticketTime; + this.startDate = startDate; + this.endDate = endDate; this.modifiedDate = LocalDateTime.now(); this.maxPrice = maxPrice; this.minPrice = minPrice; + this.posterUrl = posterUrl; return this; } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertUpdateTime.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertUpdateTime.java new file mode 100644 index 00000000..873e3c92 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertUpdateTime.java @@ -0,0 +1,31 @@ +package com.back.web7_9_codecrete_be.domain.concerts.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@RequiredArgsConstructor +public class ConcertUpdateTime { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private LocalDateTime updateTime; + + public ConcertUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } + + public ConcertUpdateTime setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + return this; + } +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertImageRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertImageRepository.java index e4790e3f..428218e0 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertImageRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertImageRepository.java @@ -1,7 +1,11 @@ package com.back.web7_9_codecrete_be.domain.concerts.repository; +import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertImage; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -9,4 +13,18 @@ @Repository public interface ConcertImageRepository extends JpaRepository { List getConcertImagesByConcert_ConcertId(Long concertConcertId); + + void deleteAllByConcert(Concert concert); + + @Modifying + @Query(""" + DELETE + FROM + ConcertImage ci + WHERE + ci.concert.concertId = :concertId +""") + void deleteByConcertId( + @Param("concertId") + Long concertId); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertUpdateTimeRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertUpdateTimeRepository.java new file mode 100644 index 00000000..80bff216 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertUpdateTimeRepository.java @@ -0,0 +1,11 @@ +package com.back.web7_9_codecrete_be.domain.concerts.repository; + +import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertUpdateTime; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; + +@Repository +public interface ConcertUpdateTimeRepository extends JpaRepository { +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/TicketOfficeRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/TicketOfficeRepository.java index 1283506c..198e58d0 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/TicketOfficeRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/TicketOfficeRepository.java @@ -3,6 +3,9 @@ import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; import com.back.web7_9_codecrete_be.domain.concerts.entity.TicketOffice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -10,4 +13,18 @@ @Repository public interface TicketOfficeRepository extends JpaRepository { List getTicketOfficesByConcert(Concert concert); + + void deleteAllByConcert(Concert concert); + + @Modifying + @Query(""" + DELETE + FROM + TicketOffice t + WHERE + t.concert.concertId = :concertId +""") + void deleteByConcertId( + @Param("concertId") + Long concertId); } 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 36bb037c..8cffc768 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 @@ -5,14 +5,9 @@ import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceDetailResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceListElement; import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceListResponse; -import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; -import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertImage; -import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertPlace; -import com.back.web7_9_codecrete_be.domain.concerts.entity.TicketOffice; -import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertImageRepository; -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 com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.result.SetResultResponse; +import com.back.web7_9_codecrete_be.domain.concerts.entity.*; +import com.back.web7_9_codecrete_be.domain.concerts.repository.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; @@ -20,9 +15,11 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestClient; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; @@ -42,6 +39,8 @@ public class KopisApiService { private final ConcertImageRepository imageRepository; + private final ConcertUpdateTimeRepository concertUpdateTimeRepository; + @Value("${kopis.api-key}") private String serviceKey; private LocalDate sdate = LocalDate.of(2025, 12, 1); @@ -49,37 +48,43 @@ public class KopisApiService { private final RestClient restClient; - public KopisApiService(ConcertRepository concertRepository, ConcertPlaceRepository placeRepository, TicketOfficeRepository ticketOfficeRepository,ConcertImageRepository imageRepository) { + public KopisApiService(ConcertRepository concertRepository, ConcertPlaceRepository placeRepository, TicketOfficeRepository ticketOfficeRepository, ConcertImageRepository imageRepository, ConcertUpdateTimeRepository concertUpdateTimeRepository) { this.concertRepository = concertRepository; this.placeRepository = placeRepository; this.ticketOfficeRepository = ticketOfficeRepository; this.imageRepository = imageRepository; + this.concertUpdateTimeRepository = concertUpdateTimeRepository; this.restClient = RestClient.builder() .baseUrl("https://kopis.or.kr/openApi/restful") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build(); } - // 데이터 조회 범위를 어떻게 할지? -> 저장은 어떻게 할지? V - // afterdate 옵션 잘 사용하기 - // 몇일 단위로 patch 하지? - // 스프링 스케줄러 이용 조회 자동화 - // 인기 장르 인기 공연 상위 노출 -> 조회수? / 평점순? - // 데이터의 범위 정하기가 까다롭구만.. - // 콘서트 장 위치가 없을 경우 가져와서 저장하기 O - public ConcertListResponse setConcertsList() throws InterruptedException { - + @Transactional + public SetResultResponse setConcertsList() throws InterruptedException { + // 최초 시작 시간 저장 + LocalDateTime now = LocalDateTime.now(); + // 콘서트 목록 받아올 Response 객체 선언 ConcertListResponse plr; + // 총 콘서트 요소를 저장할 배열 List totalConcertsList = new ArrayList<>(); // 저장시 캐시로 사용할 맵(어차피 400개 정도니까 맵 쓰는게 더 효율적으로 판단) Map concertPlaceMap = new HashMap<>(); + int addedConcerts = 0; + int addedConcertPlaces = 0; + int addedTicketOffices = 0; + int addedConcertImages = 0; + int page = 1; while (true) { + // 콘서트 목록 받아오기 plr = getConcertListResponse(serviceKey, sdate, edate, page); page++; + // 더 이상 받아올 콘서트 목록이 없으면 멈춤 if (plr.getConcertList() == null) break; + // 콘서트 요소를 콘서트 목록에서 꺼내서 더하기 for (ConcertListElement p : plr.getConcertList()) { totalConcertsList.add(p); } @@ -94,17 +99,19 @@ public ConcertListResponse setConcertsList() throws InterruptedException { ConcertDetailElement concertDetail = concertDetailResponse.getConcertDetail(); log.info("concert detail: " + concertDetailResponse.getConcertDetail()); - // 콘서트 위치 저장 -> 추후 메소드 추출하기? + // 콘서트 위치 저장 + // 콘서트 상세에서 저장할 콘서트 위치의 API ID 값 가져오기 String concertPlaceAPiKey = concertDetailResponse.getConcertDetail().getMt10id(); - ConcertPlace concertPlace; - concertPlace = concertPlaceMap.getOrDefault(concertPlaceAPiKey, placeRepository.getConcertPlaceByApiConcertPlaceId(concertPlaceAPiKey)); + // 캐시로 사용하는 맵이나 DB에서 콘서트 위치가 있는지 확인하기 + ConcertPlace concertPlace = concertPlaceMap.getOrDefault(concertPlaceAPiKey, placeRepository.getConcertPlaceByApiConcertPlaceId(concertPlaceAPiKey)); if (concertPlace == null) { - // 콘서트 장소가 null일시 + // 맵이나 DB에 없다면 API에서 해당 콘서트 위치를 가져와서 DB에 저장 후 캐시에 저장 ConcertPlaceDetailResponse concertPlaceDetailElement = getConcertPlaceDetailResponse(serviceKey, concertPlaceAPiKey); ConcertPlaceDetailElement concertPlaceDetail = concertPlaceDetailElement.getConcertPlaceDetail(); concertPlace = concertPlaceDetail.getConcertPlace(); ConcertPlace savedConcertPlace = placeRepository.save(concertPlace); concertPlaceMap.put(concertPlaceAPiKey, savedConcertPlace); + addedConcertPlaces++; log.info("concert place saved: " + savedConcertPlace); } @@ -126,48 +133,49 @@ public ConcertListResponse setConcertsList() throws InterruptedException { ); Concert savedConcert = concertRepository.save(concert); + addedConcerts++; - List ticketOfficeResponses = concertDetail.getTicketOfficeResponses(); - - for (TicketOfficeResponse ticketOffice : ticketOfficeResponses) { - TicketOffice to = new TicketOffice( - savedConcert, - ticketOffice.getTicketOfficeName(), - ticketOffice.getTicketOfficeUrl() - ); - ticketOfficeRepository.save(to); - } - - List concertImages = new ArrayList<>(); - for(String imageUrl : concertDetail.getConcertImageUrls()){ - ConcertImage concertImage = new ConcertImage(savedConcert, imageUrl); - concertImages.add(concertImage); - } - - imageRepository.saveAll(concertImages); + addedTicketOffices += saveConcertTicketOffice(concertDetail, savedConcert); + addedConcertImages += saveConcertImages(concertDetail, savedConcert); log.info("Concert saved: " + savedConcert); Thread.sleep(300); } - log.info(totalConcertsList.size() + "개의 공연 데이터 저장 완료!"); - return plr; + + ConcertUpdateTime concertUpdateTime = new ConcertUpdateTime(now); + concertUpdateTimeRepository.save(concertUpdateTime); + log.info(now + "시 기준 " + totalConcertsList.size() + "개의 공연 데이터 저장 완료!"); + return new SetResultResponse(addedConcerts,0,addedConcertPlaces,0,addedConcertImages,0,addedTicketOffices,0); } + // 매주 월요일 새벽 2시 기준으로 데이터 갱신 + @Transactional @Scheduled(cron = "0 0 2 * * Mon") - public void updateConcertData() throws InterruptedException { // 1주일 단위로 추가된 공연을 더하기 - LocalDate weekBefore = LocalDate.now().minusDays(8); - LocalDate sdate = LocalDate.now().withDayOfMonth(1); + public SetResultResponse updateConcertData() throws InterruptedException { + ConcertUpdateTime concertUpdateTime = concertUpdateTimeRepository.getReferenceById(1L); + LocalDate lastUpdatedDate = concertUpdateTime.getUpdateTime().toLocalDate(); + ConcertUpdateTime updatedTime = concertUpdateTime.setUpdateTime(LocalDateTime.now()); + LocalDate sdate = lastUpdatedDate.plusDays(1); LocalDate edate = LocalDate.now().withDayOfMonth(1).plusMonths(6); + int addedConcerts = 0; + int updatedConcerts = 0; + int addedConcertPlaces = 0; + int updatedConcertPlaces = 0; + int addedTicketOffices = 0; + int updatedTicketOffices = 0; + int addedConcertImages = 0; + int updatedConcertImages = 0; + + ConcertListResponse plr; List totalConcertsList = new ArrayList<>(); - // 저장시 캐시로 사용할 맵(어차피 400개 정도니까 맵 쓰는게 더 효율적으로 판단) Map concertPlaceMap = new HashMap<>(); int page = 1; while (true) { - plr = getConcertListResponse(serviceKey, sdate, edate, page); + plr = getConcertListResponse(serviceKey, sdate, edate, page, lastUpdatedDate); page++; if (plr.getConcertList() == null) break; for (ConcertListElement p : plr.getConcertList()) { @@ -192,15 +200,17 @@ public void updateConcertData() throws InterruptedException { // 1주일 단위 concertPlace = concertPlaceDetail.getConcertPlace(); ConcertPlace savedConcertPlace = placeRepository.save(concertPlace); concertPlaceMap.put(concertPlaceAPiKey, savedConcertPlace); + addedConcertPlaces ++; log.info("concert place saved: " + savedConcertPlace); } //콘서트 최고 금액, 최저 금액 처리. - TicketPrice ticketPrice = new TicketPrice(concertDetail.getConcertPrice()); + TicketPrice ticketPrice = new TicketPrice(concertDetail.getConcertPrice()); // 콘서트 저장 Concert concert = concertRepository.getConcertByApiConcertId(concertDetail.getApiConcertId()); - if(concert == null){ + + if (concert == null) { concert = new Concert( concertPlace, concertDetail.getConcertName(), @@ -213,41 +223,109 @@ public void updateConcertData() throws InterruptedException { // 1주일 단위 concertDetail.getPosterUrl(), concertDetail.getApiConcertId() ); + + Concert savedConcert = concertRepository.save(concert); + addedConcerts ++; + addedTicketOffices += saveConcertTicketOffice(concertDetail, savedConcert); + addedConcertImages += saveConcertImages(concertDetail, savedConcert); } else { - concert = concert.update( + concert = concert.updateByAPI( concertPlace, concertDetail.getConcertDescription(), - null, + dateStringToDateTime(concertDetail.getStartDate()), + dateStringToDateTime(concertDetail.getEndDate()), ticketPrice.maxPrice, - ticketPrice.minPrice + ticketPrice.minPrice, + concertDetail.getPosterUrl() ); + + Concert savedConcert = concertRepository.save(concert); + updatedConcerts ++; + // 기존에 저장되어 있던 연관 테이블 데이터 삭제 + ticketOfficeRepository.deleteByConcertId(savedConcert.getConcertId()); + imageRepository.deleteByConcertId(savedConcert.getConcertId()); + updatedTicketOffices += saveConcertTicketOffice(concertDetail, savedConcert); + updatedConcertImages += saveConcertImages(concertDetail, savedConcert); } - Concert savedConcert = concertRepository.save(concert); - List ticketOfficeResponses = concertDetail.getTicketOfficeResponses(); + Thread.sleep(300); + } + return new SetResultResponse(addedConcerts,updatedConcerts,addedConcertPlaces,updatedConcertPlaces,addedConcertImages,updatedConcertImages,addedTicketOffices,updatedTicketOffices); + } - for (TicketOfficeResponse ticketOffice : ticketOfficeResponses) { - TicketOffice to = new TicketOffice( - savedConcert, - ticketOffice.getTicketOfficeName(), - ticketOffice.getTicketOfficeUrl() - ); - ticketOfficeRepository.save(to); - } + @Transactional + public void concertUpdateByKopisApi(Long concertId){ + // 해당 콘서트 ID로 콘서트 객체 찾기 + Concert concert = concertRepository.getConcertByConcertId(concertId); + + // 콘서트 객체에서 API ID 값 찾아서 콘서트 상세 조회 + ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(serviceKey,concert.getApiConcertId()); + ConcertDetailElement concertDetail = concertDetailResponse.getConcertDetail(); + + // 콘서트 상세에서 콘서트 장소 API ID 값 통해서 콘서트 장소 찾기 + String concertPlaceAPiKey = concertDetailResponse.getConcertDetail().getMt10id(); + ConcertPlace concertPlace = placeRepository.getConcertPlaceByApiConcertPlaceId(concertPlaceAPiKey); + if (concertPlace == null) { + // 콘서트 장소가 null일시 -> DB에 없는 새로운 콘서트 장소 -> DB 저장 + ConcertPlaceDetailResponse concertPlaceDetailElement = getConcertPlaceDetailResponse(serviceKey, concertPlaceAPiKey); + ConcertPlaceDetailElement concertPlaceDetail = concertPlaceDetailElement.getConcertPlaceDetail(); + concertPlace = concertPlaceDetail.getConcertPlace(); + ConcertPlace savedConcertPlace = placeRepository.save(concertPlace); + log.info("concert place saved: " + savedConcertPlace); + } - List concertImages = new ArrayList<>(); - for(String imageUrl : concertDetail.getConcertImageUrls()){ - ConcertImage concertImage = new ConcertImage(savedConcert, imageUrl); - concertImages.add(concertImage); - } - imageRepository.saveAll(concertImages); + // 표 최고가, 최저가 구분 + TicketPrice ticketPrice = new TicketPrice(concertDetail.getConcertPrice()); + + // 공연의 정보를 새로운 정보로 변경 + concert = concert.updateByAPI( + concertPlace, + concertDetail.getConcertDescription(), + dateStringToDateTime(concertDetail.getStartDate()), + dateStringToDateTime(concertDetail.getEndDate()), + ticketPrice.maxPrice, + ticketPrice.minPrice, + concertDetail.getPosterUrl() + ); + + // 공연 저장 + Concert savedConcert = concertRepository.save(concert); + // 기존에 저장되어 있던 연관 테이블 데이터 삭제 + ticketOfficeRepository.deleteByConcertId(savedConcert.getConcertId()); + imageRepository.deleteByConcertId(savedConcert.getConcertId()); + // 새로 받아온 예매처, 이미지 데이터 저장 + saveConcertTicketOffice(concertDetail, savedConcert); + saveConcertImages(concertDetail, savedConcert); - log.info("Concert saved: " + savedConcert); - Thread.sleep(300); + } + + // 콘서트 예매처를 저장합니다. + private int saveConcertTicketOffice(ConcertDetailElement concertDetail, Concert savedConcert) { + List ticketOfficeResponses = concertDetail.getTicketOfficeResponses(); + + for (TicketOfficeResponse ticketOffice : ticketOfficeResponses) { + TicketOffice to = new TicketOffice( + savedConcert, + ticketOffice.getTicketOfficeName(), + ticketOffice.getTicketOfficeUrl() + ); + ticketOfficeRepository.save(to); + } + return ticketOfficeResponses.size(); + } + + // 콘서트 이미지를 저장합니다. + private int saveConcertImages(ConcertDetailElement concertDetail, Concert savedConcert) { + List concertImages = new ArrayList<>(); + for (String imageUrl : concertDetail.getConcertImageUrls()) { + ConcertImage concertImage = new ConcertImage(savedConcert, imageUrl); + concertImages.add(concertImage); } + imageRepository.saveAll(concertImages); + return concertDetail.getConcertImageUrls().size(); } public ConcertListResponse getConcertsList() { @@ -287,6 +365,7 @@ public ConcertPlaceListResponse setConcertPlace() throws InterruptedException { return pplr; } + //콘서트 목록을 조회합니다. private ConcertListResponse getConcertListResponse(String serviceKey, LocalDate sdate, LocalDate edate, int page) { ConcertListResponse performanceListResponse; performanceListResponse = restClient.get() @@ -305,7 +384,7 @@ private ConcertListResponse getConcertListResponse(String serviceKey, LocalDate } - //콘서트 목록을 조회합니다. + //특정 날짜 이후로 갱신된 콘서트 목록을 조회합니다. private ConcertListResponse getConcertListResponse(String serviceKey, LocalDate sdate, LocalDate edate, int page, LocalDate afterDate) { ConcertListResponse performanceListResponse; performanceListResponse = restClient.get() @@ -324,7 +403,6 @@ private ConcertListResponse getConcertListResponse(String serviceKey, LocalDate return performanceListResponse; } - //콘서트 상세를 조회합니다. private ConcertDetailResponse getConcertDetailResponse(String serviceKey, String concertApiId) { ConcertDetailResponse concertDetailResponse; @@ -339,6 +417,7 @@ private ConcertDetailResponse getConcertDetailResponse(String serviceKey, String } + // 콘서트 장소 목록을 조회합니다. private ConcertPlaceListResponse getConcertPlaceListResponse(String serviceKey, int page) { return restClient.get() @@ -352,7 +431,7 @@ private ConcertPlaceListResponse getConcertPlaceListResponse(String serviceKey, } // 콘서트 장소 목록을 이름 기준으로 조회합니다. - private ConcertPlaceListResponse getConcertPlaceListResponseByName(String serviceKey, String name, int page){ + private ConcertPlaceListResponse getConcertPlaceListResponseByName(String serviceKey, String name, int page) { return restClient.get() .uri(uriBuilder -> uriBuilder.path("/prfplc") @@ -377,7 +456,7 @@ private ConcertPlaceDetailResponse getConcertPlaceDetailResponse(String serviceK // API에서 출력하는 날짜 문자열을 LocalDate 객체로 변환 private LocalDate dateStringToDateTime(String dateString) { // "yyyy.MM.dd" 형식으로 들어옴 - String [] split = dateString.split("\\."); + String[] split = dateString.split("\\."); int year = Integer.parseInt(split[0]); int month = Integer.parseInt(split[1]); int day = Integer.parseInt(split[2]); @@ -385,7 +464,7 @@ private LocalDate dateStringToDateTime(String dateString) { } // 가격을 저장할 내부 객체 - private class TicketPrice{ + private class TicketPrice { int maxPrice; int minPrice;