1212import org .springframework .beans .factory .annotation .Value ;
1313import org .springframework .http .HttpHeaders ;
1414import org .springframework .http .MediaType ;
15+ import org .springframework .scheduling .annotation .Async ;
16+ import org .springframework .scheduling .annotation .EnableAsync ;
1517import org .springframework .scheduling .annotation .EnableScheduling ;
1618import org .springframework .scheduling .annotation .Scheduled ;
1719import org .springframework .stereotype .Service ;
2830
2931@ Slf4j
3032@ Service
33+ @ EnableAsync
3134@ EnableScheduling
3235public class KopisApiService {
3336 // 공연예술통합 전산망 조회를 위한 서비스 클래스입니다.
@@ -41,28 +44,46 @@ public class KopisApiService {
4144
4245 private final ConcertUpdateTimeRepository concertUpdateTimeRepository ;
4346
47+ private final ConcertRedisRepository concertRedisRepository ;
48+
4449 @ Value ("${kopis.api-key}" )
4550 private String serviceKey ;
4651 private LocalDate sdate = LocalDate .of (2025 , 12 , 1 );
47- private LocalDate edate = LocalDate .now ().plusMonths ( 6 );
52+ private LocalDate edate = LocalDate .now ().plusYears ( 1 );
4853
4954 private final RestClient restClient ;
5055
51- public KopisApiService (ConcertRepository concertRepository , ConcertPlaceRepository placeRepository , TicketOfficeRepository ticketOfficeRepository , ConcertImageRepository imageRepository , ConcertUpdateTimeRepository concertUpdateTimeRepository ) {
56+ public KopisApiService (ConcertRepository concertRepository , ConcertPlaceRepository placeRepository , TicketOfficeRepository ticketOfficeRepository , ConcertImageRepository imageRepository , ConcertUpdateTimeRepository concertUpdateTimeRepository , ConcertRedisRepository concertRedisRepository ) {
5257 this .concertRepository = concertRepository ;
5358 this .placeRepository = placeRepository ;
5459 this .ticketOfficeRepository = ticketOfficeRepository ;
5560 this .imageRepository = imageRepository ;
5661 this .concertUpdateTimeRepository = concertUpdateTimeRepository ;
62+ this .concertRedisRepository = concertRedisRepository ;
5763 this .restClient = RestClient .builder ()
5864 .baseUrl ("https://kopis.or.kr/openApi/restful" )
5965 .defaultHeader (HttpHeaders .CONTENT_TYPE , MediaType .APPLICATION_XML_VALUE )
6066 .build ();
6167 }
6268
69+ @ Async
6370 @ Transactional
64- public SetResultResponse setConcertsList () throws InterruptedException {
71+ public void setConcertsList () throws InterruptedException {
6572 // 최초 시작 시간 저장
73+ if (concertUpdateTimeRepository .count () != 0 ) {
74+ log .error ("이미 최초 저장이 되었습니다!. UpdateConcert를 통해 데이터를 갱신해주십시오!" );
75+ return ;
76+ }
77+ String key = "init" ;
78+
79+ String value = concertRedisRepository .lockGet (key );
80+ if (value != null ) {
81+ log .error ("이미 실행중인 스레드입니다." );
82+ return ;
83+ } else {
84+ concertRedisRepository .lockSave (key ,"running..." );
85+ }
86+
6687 LocalDateTime now = LocalDateTime .now ();
6788 Long startNs = System .currentTimeMillis ();
6889
@@ -79,74 +100,87 @@ public SetResultResponse setConcertsList() throws InterruptedException {
79100 int addedConcertImages = 0 ;
80101
81102 int page = 1 ;
82- while (true ) {
83- // 콘서트 목록 받아오기
84- plr = getConcertListResponse (serviceKey , sdate , edate , page );
85- page ++;
86- // 더 이상 받아올 콘서트 목록이 없으면 멈춤
87- if (plr .getConcertList () == null ) break ;
88- // 콘서트 요소를 콘서트 목록에서 꺼내서 더하기
89- for (ConcertListElement p : plr .getConcertList ()) {
90- totalConcertsList .add (p );
103+ try {
104+ while (true ) {
105+ // 콘서트 목록 받아오기
106+ plr = getConcertListResponse (serviceKey , sdate , edate , page );
107+ page ++;
108+ // 더 이상 받아올 콘서트 목록이 없으면 멈춤
109+ if (plr .getConcertList () == null ) break ;
110+ // 콘서트 요소를 콘서트 목록에서 꺼내서 더하기
111+ for (ConcertListElement p : plr .getConcertList ()) {
112+ totalConcertsList .add (p );
113+ }
114+ log .info ("Total Concert List: {}" , totalConcertsList .size () + "개의 데이터 가져오는중..." );
115+ Thread .sleep (200 );
91116 }
92- log .info ("Total Concert List: {}" , totalConcertsList .size () + "개의 데이터 가져오는중..." );
93- Thread .sleep (200 );
117+
118+ }catch (Exception e ){
119+ log .error ("공연 목록 저장 도중 오류 발생" );
120+ log .error ("오류 내용 : " + e .getMessage ());
121+ return ;
94122 }
95123
96- log .info ("Total concert list size: {}" , totalConcertsList .size ());
97- log .info ("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장" );
98- for (ConcertListElement performanceListElement : totalConcertsList ) {
99- ConcertDetailResponse concertDetailResponse = getConcertDetailResponse (serviceKey , performanceListElement .getApiConcertId ());
100- ConcertDetailElement concertDetail = concertDetailResponse .getConcertDetail ();
101124
102- // 콘서트 위치 저장
103- // 콘서트 상세에서 저장할 콘서트 위치의 API ID 값 가져오기
104- String concertPlaceAPiKey = concertDetailResponse .getConcertDetail ().getMt10id ();
105- // 캐시로 사용하는 맵이나 DB에서 콘서트 위치가 있는지 확인하기
106- ConcertPlace concertPlace = concertPlaceMap .getOrDefault (concertPlaceAPiKey , placeRepository .getConcertPlaceByApiConcertPlaceId (concertPlaceAPiKey ));
107- if (concertPlace == null ) {
108- // 맵이나 DB에 없다면 API에서 해당 콘서트 위치를 가져와서 DB에 저장 후 캐시에 저장
109- ConcertPlaceDetailResponse concertPlaceDetailElement = getConcertPlaceDetailResponse (serviceKey , concertPlaceAPiKey );
110- ConcertPlaceDetailElement concertPlaceDetail = concertPlaceDetailElement .getConcertPlaceDetail ();
111- concertPlace = concertPlaceDetail .getConcertPlace ();
112- ConcertPlace savedConcertPlace = placeRepository .save (concertPlace );
113- concertPlaceMap .put (concertPlaceAPiKey , savedConcertPlace );
114- addedConcertPlaces ++;
115- }
125+ log .info ("저장할 총 공연의 수: {}" , totalConcertsList .size ());
126+ log .info ("공연 목록 로드 완료, 공연 세부 내용 로드 및 저장" );
127+ try {
128+ for (ConcertListElement performanceListElement : totalConcertsList ) {
129+ ConcertDetailResponse concertDetailResponse = getConcertDetailResponse (serviceKey , performanceListElement .getApiConcertId ());
130+ ConcertDetailElement concertDetail = concertDetailResponse .getConcertDetail ();
131+
132+ // 콘서트 위치 저장
133+ // 콘서트 상세에서 저장할 콘서트 위치의 API ID 값 가져오기
134+ String concertPlaceAPiKey = concertDetailResponse .getConcertDetail ().getMt10id ();
135+ // 캐시로 사용하는 맵이나 DB에서 콘서트 위치가 있는지 확인하기
136+ ConcertPlace concertPlace = concertPlaceMap .getOrDefault (concertPlaceAPiKey , placeRepository .getConcertPlaceByApiConcertPlaceId (concertPlaceAPiKey ));
137+ if (concertPlace == null ) {
138+ // 맵이나 DB에 없다면 API에서 해당 콘서트 위치를 가져와서 DB에 저장 후 캐시에 저장
139+ ConcertPlaceDetailResponse concertPlaceDetailElement = getConcertPlaceDetailResponse (serviceKey , concertPlaceAPiKey );
140+ ConcertPlaceDetailElement concertPlaceDetail = concertPlaceDetailElement .getConcertPlaceDetail ();
141+ concertPlace = concertPlaceDetail .getConcertPlace ();
142+ ConcertPlace savedConcertPlace = placeRepository .save (concertPlace );
143+ concertPlaceMap .put (concertPlaceAPiKey , savedConcertPlace );
144+ addedConcertPlaces ++;
145+ }
116146
117- //콘서트 최고 금액, 최저 금액 처리.
118- TicketPrice ticketPrice = new TicketPrice (concertDetail .getConcertPrice ());
147+ //콘서트 최고 금액, 최저 금액 처리.
148+ TicketPrice ticketPrice = new TicketPrice (concertDetail .getConcertPrice ());
119149
120- // 콘서트 저장
121- Concert concert = new Concert (
122- concertPlace ,
123- concertDetail .getConcertName (),
124- concertDetail .getConcertDescription (),
125- dateStringToDateTime (concertDetail .getStartDate ()),
126- dateStringToDateTime (concertDetail .getEndDate ()),
127- null ,
128- ticketPrice .maxPrice ,
129- ticketPrice .minPrice ,
130- concertDetail .getPosterUrl (),
131- concertDetail .getApiConcertId ()
132- );
150+ // 콘서트 저장
151+ Concert concert = new Concert (
152+ concertPlace ,
153+ concertDetail .getConcertName (),
154+ concertDetail .getConcertDescription (),
155+ dateStringToDateTime (concertDetail .getStartDate ()),
156+ dateStringToDateTime (concertDetail .getEndDate ()),
157+ null ,
158+ ticketPrice .maxPrice ,
159+ ticketPrice .minPrice ,
160+ concertDetail .getPosterUrl (),
161+ concertDetail .getApiConcertId ()
162+ );
133163
134- Concert savedConcert = concertRepository .save (concert );
135- addedConcerts ++;
164+ Concert savedConcert = concertRepository .save (concert );
165+ addedConcerts ++;
136166
137- addedTicketOffices += saveConcertTicketOffice (concertDetail , savedConcert );
138- addedConcertImages += saveConcertImages (concertDetail , savedConcert );
167+ addedTicketOffices += saveConcertTicketOffice (concertDetail , savedConcert );
168+ addedConcertImages += saveConcertImages (concertDetail , savedConcert );
139169
140- Thread .sleep (300 );
170+ Thread .sleep (300 );
171+ }
172+ } catch (Exception e ) {
173+ log .error ("개별 공연 세부 내용 저장 도중 오류 발생" );
174+ log .error ("오류 내용 : " + e .getMessage ());
175+ return ;
141176 }
142-
143177 ConcertUpdateTime concertUpdateTime = new ConcertUpdateTime (now );
144178 concertUpdateTimeRepository .save (concertUpdateTime );
145179 log .info (now + "시 기준 " + totalConcertsList .size () + "개의 공연 데이터 저장 완료!" );
146180 long endNs = System .currentTimeMillis ();
147181 long durationSec = ((endNs - startNs ) / 1000 );
148182 log .info (durationSec /60 + "분, " + durationSec % 60 + "초 소요되었습니다." );
149- return new SetResultResponse ( addedConcerts , 0 , addedConcertPlaces , 0 , addedConcertImages , 0 , addedTicketOffices , 0 );
183+ concertRedisRepository . unlockSave ( key );
150184 }
151185
152186
@@ -157,8 +191,8 @@ public SetResultResponse updateConcertData() throws InterruptedException {
157191 ConcertUpdateTime concertUpdateTime = concertUpdateTimeRepository .getReferenceById (1L );
158192 LocalDate lastUpdatedDate = concertUpdateTime .getUpdateTime ().toLocalDate ();
159193 ConcertUpdateTime updatedTime = concertUpdateTime .setUpdateTime (LocalDateTime .now ());
160- LocalDate sdate = lastUpdatedDate . plusDays ( 1 ) ;
161- LocalDate edate = LocalDate .now ().withDayOfMonth ( 1 ). plusMonths ( 6 );
194+ LocalDate sdate = lastUpdatedDate ;
195+ LocalDate edate = LocalDate .now ().plusYears ( 1 );
162196
163197 int addedConcerts = 0 ;
164198 int updatedConcerts = 0 ;
0 commit comments