package com.hlab.yanalyst.domain.channel; import com.hlab.yanalyst.web.dto.YoutubeSearchResultDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * 조회수 검색 결과(YoutubeSearchResultDto)를 수집함(ChannelVideo, source=SEARCH)으로 영속화한다. * 이미 수집된 videoId 는 건너뛴다(중복 방지). */ @Slf4j @Service @RequiredArgsConstructor public class SearchCollectionService { private final ChannelVideoRepository channelVideoRepository; @Transactional public CollectResult collectFromSearch(List items) { if (items == null || items.isEmpty()) { return new CollectResult(0, 0, List.of()); } int saved = 0; int skipped = 0; List savedIds = new ArrayList<>(); for (YoutubeSearchResultDto dto : items) { if (dto.getVideoId() == null || dto.getVideoId().isBlank()) { skipped++; continue; } if (channelVideoRepository.existsByVideoId(dto.getVideoId())) { skipped++; // 이미 수집된 영상 continue; } BigDecimal viewsPerHour = VideoMetrics.viewsPerHour(dto.getViewCount(), dto.getPublishedAt()); BigDecimal viewsPerSubRatio = VideoMetrics.viewsPerSubRatio(dto.getViewCount(), dto.getSubscriberCount()); String hashtags = (dto.getHashtags() == null || dto.getHashtags().isEmpty()) ? null : String.join(",", dto.getHashtags()); ChannelVideo video = ChannelVideo.fromSearch( dto.getVideoId(), dto.getTitle(), dto.getThumbnailUrl(), dto.getPublishedAt(), dto.getViewCount(), dto.getChannelId(), dto.getChannelTitle(), dto.getSubscriberCount(), dto.getDurationSec(), viewsPerHour, viewsPerSubRatio, hashtags); channelVideoRepository.save(video); saved++; savedIds.add(dto.getVideoId()); } log.info("Search collection done. saved={}, skipped(duplicate/invalid)={}", saved, skipped); return new CollectResult(saved, skipped, savedIds); } /** 수집 결과 요약. */ public record CollectResult(int saved, int skipped, List savedVideoIds) {} }