Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand All @@ -30,8 +31,9 @@ public class ConcertAdminController { // todo : 인증 권한 추가하기

@Operation(summary = "초기 공연 정보 저장", description = "25년 12월부터 앞으로 6개월 이후까지의 전체 공연의 정보를 가져와서 저장합니다. 대략 10~12분 정도 시간이 소요됩니다.")
@PostMapping("setConcertData")
public RsData<SetResultResponse> setConcert() throws InterruptedException {
return RsData.success(kopisApiService.setConcertsList());
public RsData<Void> setConcert() throws InterruptedException {
kopisApiService.setConcertsList();
return RsData.success(HttpStatus.ACCEPTED,"저장 요청을 보냈습니다",null);
}

@Operation(summary = "공연 정보 갱신", description = "공연 정보를 직접 갱신합니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.back.web7_9_codecrete_be.domain.concerts.repository;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import java.util.concurrent.TimeUnit;

@Repository
@RequiredArgsConstructor
public class ConcertRedisRepository {
private final RedisTemplate<String,String> redisTemplate;

private static final String LOCK_FLAG_PREFIX = "initLoad: ";

public void lockSave(String key, String value) {
redisTemplate.opsForValue().set(
LOCK_FLAG_PREFIX + key,
value,
900,
TimeUnit.SECONDS);
}

public String lockGet(String key) {
return redisTemplate.opsForValue().get(LOCK_FLAG_PREFIX + key);
}

public void unlockSave(String key) {
redisTemplate.delete(LOCK_FLAG_PREFIX + key);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
Expand All @@ -28,6 +30,7 @@

@Slf4j
@Service
@EnableAsync
@EnableScheduling
public class KopisApiService {
// 공연예술통합 전산망 조회를 위한 서비스 클래스입니다.
Expand All @@ -41,28 +44,46 @@ public class KopisApiService {

private final ConcertUpdateTimeRepository concertUpdateTimeRepository;

private final ConcertRedisRepository concertRedisRepository;

@Value("${kopis.api-key}")
private String serviceKey;
private LocalDate sdate = LocalDate.of(2025, 12, 1);
private LocalDate edate = LocalDate.now().plusMonths(6);
private LocalDate edate = LocalDate.now().plusYears(1);

private final RestClient restClient;

public KopisApiService(ConcertRepository concertRepository, ConcertPlaceRepository placeRepository, TicketOfficeRepository ticketOfficeRepository, ConcertImageRepository imageRepository, ConcertUpdateTimeRepository concertUpdateTimeRepository) {
public KopisApiService(ConcertRepository concertRepository, ConcertPlaceRepository placeRepository, TicketOfficeRepository ticketOfficeRepository, ConcertImageRepository imageRepository, ConcertUpdateTimeRepository concertUpdateTimeRepository,ConcertRedisRepository concertRedisRepository) {
this.concertRepository = concertRepository;
this.placeRepository = placeRepository;
this.ticketOfficeRepository = ticketOfficeRepository;
this.imageRepository = imageRepository;
this.concertUpdateTimeRepository = concertUpdateTimeRepository;
this.concertRedisRepository = concertRedisRepository;
this.restClient = RestClient.builder()
.baseUrl("https://kopis.or.kr/openApi/restful")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build();
}

@Async
@Transactional
public SetResultResponse setConcertsList() throws InterruptedException {
public void setConcertsList() throws InterruptedException {
// 최초 시작 시간 저장
if(concertUpdateTimeRepository.count() != 0) {
log.error("이미 최초 저장이 되었습니다!. UpdateConcert를 통해 데이터를 갱신해주십시오!");
return;
}
String key = "init";

String value = concertRedisRepository.lockGet(key);
if(value != null) {
log.error("이미 실행중인 스레드입니다.");
return;
} else {
concertRedisRepository.lockSave(key,"running...");
}

LocalDateTime now = LocalDateTime.now();
Long startNs = System.currentTimeMillis();

Expand All @@ -79,74 +100,87 @@ public SetResultResponse setConcertsList() throws InterruptedException {
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);
try{
while (true) {
// 콘서트 목록 받아오기
plr = getConcertListResponse(serviceKey, sdate, edate, page);
page++;
// 더 이상 받아올 콘서트 목록이 없으면 멈춤
if (plr.getConcertList() == null) break;
// 콘서트 요소를 콘서트 목록에서 꺼내서 더하기
for (ConcertListElement p : plr.getConcertList()) {
totalConcertsList.add(p);
}
log.info("Total Concert List: {}", totalConcertsList.size() + "개의 데이터 가져오는중...");
Thread.sleep(200);
}
log.info("Total Concert List: {}", totalConcertsList.size() + "개의 데이터 가져오는중...");
Thread.sleep(200);

}catch (Exception e){
log.error("공연 목록 저장 도중 오류 발생");
log.error("오류 내용 : " + e.getMessage());
return;
}

log.info("Total concert list size: {}", totalConcertsList.size());
log.info("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장");
for (ConcertListElement performanceListElement : totalConcertsList) {
ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(serviceKey, performanceListElement.getApiConcertId());
ConcertDetailElement concertDetail = concertDetailResponse.getConcertDetail();

// 콘서트 위치 저장
// 콘서트 상세에서 저장할 콘서트 위치의 API ID 값 가져오기
String concertPlaceAPiKey = concertDetailResponse.getConcertDetail().getMt10id();
// 캐시로 사용하는 맵이나 DB에서 콘서트 위치가 있는지 확인하기
ConcertPlace concertPlace = concertPlaceMap.getOrDefault(concertPlaceAPiKey, placeRepository.getConcertPlaceByApiConcertPlaceId(concertPlaceAPiKey));
if (concertPlace == 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("저장할 총 공연의 수: {}", totalConcertsList.size());
log.info("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장");
try {
for (ConcertListElement performanceListElement : totalConcertsList) {
ConcertDetailResponse concertDetailResponse = getConcertDetailResponse(serviceKey, performanceListElement.getApiConcertId());
ConcertDetailElement concertDetail = concertDetailResponse.getConcertDetail();

// 콘서트 위치 저장
// 콘서트 상세에서 저장할 콘서트 위치의 API ID 값 가져오기
String concertPlaceAPiKey = concertDetailResponse.getConcertDetail().getMt10id();
// 캐시로 사용하는 맵이나 DB에서 콘서트 위치가 있는지 확인하기
ConcertPlace concertPlace = concertPlaceMap.getOrDefault(concertPlaceAPiKey, placeRepository.getConcertPlaceByApiConcertPlaceId(concertPlaceAPiKey));
if (concertPlace == 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++;
}

//콘서트 최고 금액, 최저 금액 처리.
TicketPrice ticketPrice = new TicketPrice(concertDetail.getConcertPrice());
//콘서트 최고 금액, 최저 금액 처리.
TicketPrice ticketPrice = new TicketPrice(concertDetail.getConcertPrice());

// 콘서트 저장
Concert concert = new Concert(
concertPlace,
concertDetail.getConcertName(),
concertDetail.getConcertDescription(),
dateStringToDateTime(concertDetail.getStartDate()),
dateStringToDateTime(concertDetail.getEndDate()),
null,
ticketPrice.maxPrice,
ticketPrice.minPrice,
concertDetail.getPosterUrl(),
concertDetail.getApiConcertId()
);
// 콘서트 저장
Concert concert = new Concert(
concertPlace,
concertDetail.getConcertName(),
concertDetail.getConcertDescription(),
dateStringToDateTime(concertDetail.getStartDate()),
dateStringToDateTime(concertDetail.getEndDate()),
null,
ticketPrice.maxPrice,
ticketPrice.minPrice,
concertDetail.getPosterUrl(),
concertDetail.getApiConcertId()
);

Concert savedConcert = concertRepository.save(concert);
addedConcerts++;
Concert savedConcert = concertRepository.save(concert);
addedConcerts++;

addedTicketOffices += saveConcertTicketOffice(concertDetail, savedConcert);
addedConcertImages += saveConcertImages(concertDetail, savedConcert);
addedTicketOffices += saveConcertTicketOffice(concertDetail, savedConcert);
addedConcertImages += saveConcertImages(concertDetail, savedConcert);

Thread.sleep(300);
Thread.sleep(300);
}
} catch (Exception e) {
log.error("개별 공연 세부 내용 저장 도중 오류 발생");
log.error("오류 내용 : " + e.getMessage());
return ;
}

ConcertUpdateTime concertUpdateTime = new ConcertUpdateTime(now);
concertUpdateTimeRepository.save(concertUpdateTime);
log.info(now + "시 기준 " + totalConcertsList.size() + "개의 공연 데이터 저장 완료!");
long endNs = System.currentTimeMillis();
long durationSec = ((endNs - startNs) / 1000);
log.info(durationSec/60 + "분, " + durationSec % 60 + "초 소요되었습니다." );
return new SetResultResponse(addedConcerts,0,addedConcertPlaces,0,addedConcertImages,0,addedTicketOffices,0);
concertRedisRepository.unlockSave(key);
}


Expand All @@ -157,8 +191,8 @@ 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);
LocalDate sdate = lastUpdatedDate;
LocalDate edate = LocalDate.now().plusYears(1);

int addedConcerts = 0;
int updatedConcerts = 0;
Expand Down