diff --git a/admin/src/test/java/itcast/AdminNewsServiceTest.java b/admin/src/test/java/itcast/AdminNewsServiceTest.java index fc49f256..3dd822ef 100644 --- a/admin/src/test/java/itcast/AdminNewsServiceTest.java +++ b/admin/src/test/java/itcast/AdminNewsServiceTest.java @@ -160,7 +160,7 @@ public void successNewsRetrieve() { Long userId = 1L; Long newsId = 1L; LocalDateTime fixedTime = LocalDateTime.of(2024, 12, 1, 12, 0); - LocalDate sendAt = LocalDate.of(2024, 12, 1); + LocalDate fixedDate2 = LocalDate.of(2024, 12, 1); User user = User.builder() .id(userId) diff --git a/common/src/main/java/itcast/exception/ErrorCodes.java b/common/src/main/java/itcast/exception/ErrorCodes.java index 3b0b3c6c..6d68c89c 100644 --- a/common/src/main/java/itcast/exception/ErrorCodes.java +++ b/common/src/main/java/itcast/exception/ErrorCodes.java @@ -33,6 +33,7 @@ public enum ErrorCodes { NEWS_CRAWLING_ERROR("뉴스 크롤링에 실패했습니다", 2004L, HttpStatus.BAD_REQUEST), BLOG_SELECT_ERROR("블로그 선택에 실패하였습니다.", 2004L, HttpStatus.BAD_REQUEST), NEWS_SELECT_ERROR("뉴스 선택에 실패하였습니다.", 2004L, HttpStatus.BAD_REQUEST), + GPT_SERVICE_ERROR("GPT요약 중 오류가 발생했습니다 ",2009L,HttpStatus.BAD_REQUEST), TODAY_NEWS_NOT_FOUND("뉴스 선택에 실패했습니다", 2005L, HttpStatus.NOT_FOUND), NOT_FOUND_EMAIL("이메일을 찾을 수 없습니다", 2006L, HttpStatus.NOT_FOUND), diff --git a/schedule/src/main/java/itcast/ai/dto/request/GPTSummaryRequest.java b/schedule/src/main/java/itcast/ai/dto/request/GPTSummaryRequest.java index 50e00340..356921b2 100644 --- a/schedule/src/main/java/itcast/ai/dto/request/GPTSummaryRequest.java +++ b/schedule/src/main/java/itcast/ai/dto/request/GPTSummaryRequest.java @@ -1,8 +1,17 @@ package itcast.ai.dto.request; +import lombok.Builder; + + public record GPTSummaryRequest( String model, Message message, float temperature ) { + @Builder + public GPTSummaryRequest(String model, Message message, float temperature) { + this.model = model; + this.message = message; + this.temperature = temperature; + } } diff --git a/schedule/src/main/java/itcast/news/application/NewsService.java b/schedule/src/main/java/itcast/news/application/NewsService.java index 40cedba8..038c8f6d 100644 --- a/schedule/src/main/java/itcast/news/application/NewsService.java +++ b/schedule/src/main/java/itcast/news/application/NewsService.java @@ -39,49 +39,67 @@ public void newsCrawling() throws IOException { List links = findLinks(URL); links = isValidLinks(links); + List newsList = new ArrayList<>(); + links.forEach(link -> { - try { - Document url = Jsoup.connect(link).get(); - String titles = url.select("#title_area").text(); - String content = url.select("#dic_area").text(); - String date = - url.select(".media_end_head_info_datestamp_bunch").text(); - String thumbnail = - url.selectFirst("meta[property=og:image]").attr("content"); - - titles = cleanContent(titles); - content = cleanContent(content); - LocalDateTime publishedAt = convertDateTime(date); + News news = processNews(link); + if (news != null) { + newsList.add(news); + } + }); + if (!newsList.isEmpty()) { + newsRepository.saveAll(newsList); + } + } + + public News processNews(String link) { + try { + Document url = Jsoup.connect(link).get(); + String titles = url.select("#title_area").text(); + String content = url.select("#dic_area").text(); + String date = url.select(".media_end_head_info_datestamp_bunch").text(); + String thumbnail = url.selectFirst("meta[property=og:image]").attr("content"); + + titles = cleanContent(titles); + content = cleanContent(content); + LocalDateTime publishedAt = convertDateTime(date); + + if (thumbnail.isEmpty()) { + throw new ItCastApplicationException(INVALID_NEWS_CONTENT); + } if (thumbnail.isEmpty()) { log.error("썸네일이 존재하지 않습니다. {}", link); throw new ItCastApplicationException(INVALID_NEWS_CONTENT); } - CreateNewsRequest newsRequest = new CreateNewsRequest(titles, content, link, thumbnail, publishedAt); - News news = newsRepository.save(newsRequest.toEntity(titles, content, link, thumbnail, publishedAt)); - Message message = new Message("user", content); - GPTSummaryRequest request = new GPTSummaryRequest("gpt-4o-mini", message, 0.7f); - gptService.updateNewsBySummaryContent(request, news.getId()); - } catch (IOException e) { - throw new ItCastApplicationException(NEWS_CRAWLING_ERROR); - } - }); + CreateNewsRequest newsRequest = new CreateNewsRequest(titles, content, link, thumbnail, publishedAt); + News news = newsRequest.toEntity(titles, content, link, thumbnail, publishedAt); + updateNewsSummary(news, content); + return news; + } catch (IOException e) { + throw new ItCastApplicationException(CRAWLING_PARSE_ERROR); + } + } + + public void updateNewsSummary(News news, String content) { + try { + Message message = new Message("user", content); + GPTSummaryRequest request = new GPTSummaryRequest("gpt-4o-mini", message, 0.7f); + gptService.updateNewsBySummaryContent(request, news.getId()); + } catch (Exception e) { + throw new ItCastApplicationException(GPT_SERVICE_ERROR); + } } public List findLinks(String url) throws IOException { Document document = Jsoup.connect(url).get(); Elements articles = document.select(".sa_thumb_inner"); - List links = new ArrayList<>(); - articles.forEach(article -> { - if (links.size() >= LINK_SIZE) { - return; - } - String link = article.select("a").attr("href"); - links.add(link); - }); - return links; + return articles.stream() + .map(article -> article.select("a").attr("href")) + .limit(LINK_SIZE) + .toList(); } public List isValidLinks(List links) { @@ -127,9 +145,8 @@ public String cleanContent(String info) { throw new ItCastApplicationException(INVALID_NEWS_CONTENT); } - info = info.replaceAll("\\[.*?\\]", "") + return info.replaceAll("\\[.*?\\]", "") .replaceAll("\\(.*?\\)", "") .trim(); - return info; } } diff --git a/schedule/src/main/java/itcast/news/application/SendNewsService.java b/schedule/src/main/java/itcast/news/application/SendNewsService.java index c0076e73..9996bbd4 100644 --- a/schedule/src/main/java/itcast/news/application/SendNewsService.java +++ b/schedule/src/main/java/itcast/news/application/SendNewsService.java @@ -39,7 +39,6 @@ public void selectNews(LocalDate yesterday) { } LocalDate sendAt = LocalDate.now().plusDays(ALARM_DAY); - newsList.forEach(news -> { news.newsUpdate(sendAt); }); @@ -60,7 +59,6 @@ public void sendNews() { news.getLink(), news.getThumbnail())) .toList(); - List emails = retrieveUserEmails(Interest.NEWS); if (emails == null || emails.isEmpty()) { diff --git a/schedule/src/test/java/itcast/news/application/NewsServiceTest.java b/schedule/src/test/java/itcast/news/application/NewsServiceTest.java index 92ba26b6..3ca2edbd 100644 --- a/schedule/src/test/java/itcast/news/application/NewsServiceTest.java +++ b/schedule/src/test/java/itcast/news/application/NewsServiceTest.java @@ -1,6 +1,7 @@ package itcast.news.application; import itcast.ai.application.GPTService; +import itcast.ai.dto.request.GPTSummaryRequest; import itcast.domain.news.News; import itcast.news.repository.NewsRepository; import org.jsoup.Connection; @@ -11,16 +12,15 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Arrays; -import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -168,4 +168,31 @@ void findLinksTest() throws IOException { } } + @Test + @DisplayName("updateNewsSummary 메소드 테스트") + void updateNewsSummaryTest() { + // give + News news = News.builder() + .id(1L) + .title("Sample News") + .content("Original Content") + .build(); + String content = "Updated Summary Content"; + + doNothing().when(gptService).updateNewsBySummaryContent(any(GPTSummaryRequest.class), eq(news.getId())); + + // when + assertDoesNotThrow(() -> newsService.updateNewsSummary(news, content)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(GPTSummaryRequest.class); + verify(gptService, times(1)).updateNewsBySummaryContent(captor.capture(), eq(news.getId())); + + // then + GPTSummaryRequest capturedRequest = captor.getValue(); + assertEquals("gpt-4o-mini", capturedRequest.model()); + assertEquals("user", capturedRequest.message().getRole()); + assertEquals(content, capturedRequest.message().getContent()); + assertEquals(0.7f, capturedRequest.temperature()); + } + } diff --git a/schedule/src/test/java/itcast/news/application/SelectNewsServiceTest.java b/schedule/src/test/java/itcast/news/application/SelectNewsServiceTest.java index 8d45ba8d..440fa17f 100644 --- a/schedule/src/test/java/itcast/news/application/SelectNewsServiceTest.java +++ b/schedule/src/test/java/itcast/news/application/SelectNewsServiceTest.java @@ -27,8 +27,6 @@ @ExtendWith(MockitoExtension.class) public class SelectNewsServiceTest { private static final int YESTERDAY = 1; - private static final int ALARM_HOUR = 2; - private static final int ALARM_DAY = 2; @Mock private UserRepository userRepository;