package com.hlab.yanalyst.domain.channel; import com.hlab.yanalyst.global.schedule.YoutubeQuotaGuard; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 소재 발굴 피드 수집기. * *
SOURCE(웹예능 공식채널)와 RIVAL(경쟁 클립 채널)의 신규 롱폼을 주기적으로 받아온다
* (min-duration-sec 미만의 짧은 클립·쇼츠는 수집하지 않는다).
* uploads 플레이리스트 1페이지만 조회하므로 채널당 약 2 units — 검색(search.list 100 units)보다 훨씬 싸다.
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FeedCollectionService {
/** 채널 1개 수집 추정 쿼터: playlistItems(1) + videos.list(1). */
private static final long EST_UNITS_PER_CHANNEL = 2;
private final ChannelRepository channelRepository;
private final ChannelService channelService;
private final YoutubeQuotaGuard quotaGuard;
@Value("${hlab.feed.enabled:true}")
private boolean enabled;
/** 이 일수보다 오래된 업로드는 수집하지 않는다(과거 전체를 끌어오지 않기 위함). */
@Value("${hlab.feed.period-days:14}")
private int periodDays;
/** 이 길이(초) 미만은 수집하지 않는다 — 자를 원본이 못 되는 짧은 클립·쇼츠 차단. 기본 10분. */
@Value("${hlab.feed.min-duration-sec:600}")
private int minDurationSec;
/**
* role 컬럼이 없던 시절의 기존 채널은 role 이 null 이다. 부팅 시 1회 MY 로 백필한다.
* (ddl-auto:update 는 DEFAULT 를 채워주지 않으므로 애플리케이션에서 처리)
*/
@EventListener(ApplicationReadyEvent.class)
@Transactional
public void backfillChannelRoles() {
int updated = channelRepository.backfillNullRoles();
if (updated > 0) log.info("[Feed] 채널 role 백필: {}건 → MY", updated);
}
@Scheduled(cron = "${hlab.feed.cron:0 15 */3 * * *}")
public void scheduledCollect() {
if (!enabled) {
log.info("[Feed] 피드 자동 수집 비활성화됨 (hlab.feed.enabled=false)");
return;
}
log.info("[Feed] 자동 수집 완료: {}", collectAll());
}
/** 수동/스케줄 공용. 모든 피드 시드를 쿼터 한도 안에서 수집하고 요약을 반환한다. */
public Map