본문 바로가기

Playlist/Open API

TheMovieApiService

*/domain/content/api/service

 

import com.codeit.playlist.domain.content.api.response.TheMovieListResponse;
import com.codeit.playlist.domain.content.api.response.TheMovieResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

import java.time.Duration;

@Slf4j
@Service
@RequiredArgsConstructor
public class TheMovieApiService {
    private final WebClient webClient;

    @Value("${TMDB_API_KEY}")
    private String apikey; // tmdb API key

    private final int firstPage = 1;
    private final int maxPage = 5;

    private Mono<TheMovieListResponse> callTheMovieApi(String query, String path, int page) {
        log.info("[콘텐츠 데이터 관리] TheMovie API Mono 빌드 시작");
        return webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .scheme("https")
                        .host("api.themoviedb.org")
                        .path(path)
                        .queryParam("api_key", apikey)
                        .query(query + "&page=" + page)
                        .build())
                .retrieve()
                .onStatus(s -> s.value() == 429, clientResponse -> clientResponse.createException().flatMap(Mono::error))
                .bodyToMono(TheMovieListResponse.class)
                .retryWhen(
                        Retry.backoff(3, Duration.ofSeconds(2))
                                .filter(exception -> exception instanceof WebClientResponseException webException && webException.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS)
                                .transientErrors(true)
                )
                .doOnError(WebClientResponseException.class,
                        e -> log.error("[콘텐츠 데이터 관리] TheMovie API Mono {} 수집 오류 : status = {}, body = {}", query, e.getStatusCode(), e.getResponseBodyAsString()));
    }

    private Flux<TheMovieResponse> fluxingTheMovieApi(String query, String path) {
        log.info("[콘텐츠 데이터 관리] TheMovie API Flux 빌드 시작");
        return callTheMovieApi(query, path, firstPage)
                    .flatMapMany(firstPageResponse -> {
                       if(firstPageResponse.results() == null || firstPageResponse.results().isEmpty()) {
                           return Flux.empty();
                       }

                       int totalPages = firstPageResponse.totalPages();
                        int maxPages = Math.min(totalPages, maxPage);

                       Flux<TheMovieResponse> firstFluxResult = Flux.fromIterable(firstPageResponse.results());
                       if(totalPages <= 1) { // 만약 totalPages가 1보다 작거나 같다면, firstPage를 그대로 내보냄
                           return firstFluxResult;
                       }

                       Flux<TheMovieResponse> afterFluxResult = Flux.range(2, maxPages - 1) // 2부터 maxPage까지
                               .concatMap(page ->
                                       callTheMovieApi(query, path, page)
                                               .flatMapMany(afterResponse -> {
                                                   if(afterResponse.results() == null || afterResponse.results().isEmpty()) {
                                                       return Flux.empty();
                                                   }
                                                   return Flux.fromIterable(afterResponse.results());
                                               })
                               );
                       return Flux.concat(firstFluxResult, afterFluxResult);
                    });
    }

    @Transactional
    public Flux<TheMovieResponse> getApiMovie(String query) {
        return fluxingTheMovieApi(query, "/3/discover/movie");
    }
}

 

'Playlist > Open API' 카테고리의 다른 글

TmdbTagApiService.java  (0) 2025.12.17
TvSeriesApiService  (0) 2025.12.17
Open API 사용하기 : The Movie (진행중)  (0) 2025.11.20
Open API : The Sports  (1) 2025.11.20
Open API : TheMovie  (0) 2025.11.20