diff --git a/backend/build.gradle b/backend/build.gradle index f91e407e..b04a3634 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -32,6 +32,8 @@ dependencies { compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' + implementation 'org.springframework.boot:spring-boot-starter-amqp' + annotationProcessor 'org.projectlombok:lombok' // jsoup 의존성 추가 diff --git a/backend/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java b/backend/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java index 832d55d9..a0daaa44 100644 --- a/backend/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java +++ b/backend/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java @@ -1,6 +1,6 @@ package com.rollthedice.backend.domain.crawling; -import com.rollthedice.backend.domain.dto.NewsUrlDto; +import com.rollthedice.backend.domain.news.dto.NewsUrlDto; import com.rollthedice.backend.domain.news.entity.News; import com.rollthedice.backend.domain.news.service.NewsCategory; import com.rollthedice.backend.domain.news.service.NewsService; @@ -37,6 +37,7 @@ public void scrap() throws IOException { scrapNewsContentsAndUpdate(categoryName, news); } } + newsService.summarizeNewsContent(); } private void scrapNewsUrls(String categoryUrl) throws IOException { diff --git a/backend/src/main/java/com/rollthedice/backend/domain/news/contentqueue/ContentProducer.java b/backend/src/main/java/com/rollthedice/backend/domain/news/contentqueue/ContentProducer.java new file mode 100644 index 00000000..5ec21939 --- /dev/null +++ b/backend/src/main/java/com/rollthedice/backend/domain/news/contentqueue/ContentProducer.java @@ -0,0 +1,26 @@ +package com.rollthedice.backend.domain.news.contentqueue; + +import com.rollthedice.backend.domain.news.dto.ContentMessageDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class ContentProducer { + @Value("${rabbitmq.summary.exchange.name}") + private String exchangeName; + + @Value("${rabbitmq.summary.routing.key}") + private String routingKey; + + private final RabbitTemplate rabbitTemplate; + + public void sendMessage(ContentMessageDto messageDto) { + log.info("publish news content message : {}", messageDto.getId()); + rabbitTemplate.convertAndSend(exchangeName, routingKey, messageDto); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rollthedice/backend/domain/news/contentqueue/SummarizedContentConsumer.java b/backend/src/main/java/com/rollthedice/backend/domain/news/contentqueue/SummarizedContentConsumer.java new file mode 100644 index 00000000..28db899f --- /dev/null +++ b/backend/src/main/java/com/rollthedice/backend/domain/news/contentqueue/SummarizedContentConsumer.java @@ -0,0 +1,21 @@ +package com.rollthedice.backend.domain.news.contentqueue; + +import com.rollthedice.backend.domain.news.dto.ContentMessageDto; +import com.rollthedice.backend.domain.news.service.NewsService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class SummarizedContentConsumer { + private final NewsService newsService; + + @RabbitListener(queues = "${rabbitmq.store.queue.name}") + public void receiveMessage(ContentMessageDto messageDto) { + log.info("Received summarized news message id: {}", messageDto.getId()); + newsService.updateSummarizedNews(messageDto); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rollthedice/backend/domain/news/dto/ContentMessageDto.java b/backend/src/main/java/com/rollthedice/backend/domain/news/dto/ContentMessageDto.java new file mode 100644 index 00000000..6415a4c2 --- /dev/null +++ b/backend/src/main/java/com/rollthedice/backend/domain/news/dto/ContentMessageDto.java @@ -0,0 +1,12 @@ +package com.rollthedice.backend.domain.news.dto; + +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ContentMessageDto { + private Long id; + private String content; +} diff --git a/backend/src/main/java/com/rollthedice/backend/domain/dto/NewsUrlDto.java b/backend/src/main/java/com/rollthedice/backend/domain/news/dto/NewsUrlDto.java similarity index 83% rename from backend/src/main/java/com/rollthedice/backend/domain/dto/NewsUrlDto.java rename to backend/src/main/java/com/rollthedice/backend/domain/news/dto/NewsUrlDto.java index f1cfd418..377e763b 100644 --- a/backend/src/main/java/com/rollthedice/backend/domain/dto/NewsUrlDto.java +++ b/backend/src/main/java/com/rollthedice/backend/domain/news/dto/NewsUrlDto.java @@ -1,4 +1,4 @@ -package com.rollthedice.backend.domain.dto; +package com.rollthedice.backend.domain.news.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/backend/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java b/backend/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java index 14614f78..d92bc0f7 100644 --- a/backend/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java +++ b/backend/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java @@ -1,13 +1,17 @@ package com.rollthedice.backend.domain.news.service; -import com.rollthedice.backend.domain.dto.NewsUrlDto; +import com.rollthedice.backend.domain.news.contentqueue.ContentProducer; +import com.rollthedice.backend.domain.news.dto.ContentMessageDto; +import com.rollthedice.backend.domain.news.dto.NewsUrlDto; import com.rollthedice.backend.domain.news.entity.News; import com.rollthedice.backend.domain.news.repository.NewsRepository; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.List; @Slf4j @@ -15,6 +19,7 @@ @RequiredArgsConstructor public class NewsService { private final NewsRepository newsRepository; + private final ContentProducer contentProducer; @Transactional public void addNews(NewsUrlDto dto) { @@ -26,4 +31,18 @@ public List getAllNews() { return newsRepository.findAll(); } + @Transactional + public void updateSummarizedNews(ContentMessageDto messageDto) { + News news = newsRepository.findById(messageDto.getId()) + .orElseThrow(EntityNotFoundException::new); + news.updateSummarizedContent(messageDto.getContent()); + } + + @Transactional(readOnly = true) + public void summarizeNewsContent() { + List messages = new ArrayList<>(); + newsRepository.findAll().forEach(n -> + messages.add(new ContentMessageDto(n.getId(), n.getContent()))); + messages.forEach(contentProducer::sendMessage); + } } diff --git a/backend/src/main/java/com/rollthedice/backend/global/config/RabbitMQConfig.java b/backend/src/main/java/com/rollthedice/backend/global/config/RabbitMQConfig.java new file mode 100644 index 00000000..24ccde74 --- /dev/null +++ b/backend/src/main/java/com/rollthedice/backend/global/config/RabbitMQConfig.java @@ -0,0 +1,100 @@ +package com.rollthedice.backend.global.config; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitMQConfig { + + @Value("${spring.rabbitmq.host}") + private String rabbitmqHost; + + @Value("${spring.rabbitmq.port}") + private int rabbitmqPort; + + @Value("${spring.rabbitmq.username}") + private String rabbitmqUsername; + + @Value("${spring.rabbitmq.password}") + private String rabbitmqPassword; + + @Value("${rabbitmq.summary.queue.name}") + private String summaryQueueName; + + @Value("${rabbitmq.store.queue.name}") + private String storeQueueName; + + @Value("${rabbitmq.summary.exchange.name}") + private String summaryExchangeName; + + @Value(("${rabbitmq.store.exchange.name}")) + private String storeExchangeName; + + @Value("${rabbitmq.summary.routing.key}") + private String summaryRoutingKey; + + @Value("${rabbitmq.store.routing.key}") + private String storeRoutingKey; + + @Bean + public Queue summaryQueue() { + return new Queue(summaryQueueName); + } + + @Bean + public Queue storeQueue() { + return new Queue(storeQueueName); + } + + @Bean + public DirectExchange summaryExchange() { + return new DirectExchange(summaryExchangeName); + } + + @Bean + public DirectExchange storeExchange() { + return new DirectExchange(storeExchangeName); + } + + @Bean + public Binding summaryBinding(Queue summaryQueue, DirectExchange summaryExchange) { + return BindingBuilder.bind(summaryQueue).to(summaryExchange).with(summaryRoutingKey); + } + + @Bean + public Binding storeBinding(Queue storeQueue, DirectExchange storeExchange) { + return BindingBuilder.bind(storeQueue).to(storeExchange).with(storeRoutingKey); + } + + @Bean + public ConnectionFactory connectionFactory() { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setHost(rabbitmqHost); + connectionFactory.setPort(rabbitmqPort); + connectionFactory.setUsername(rabbitmqUsername); + connectionFactory.setPassword(rabbitmqPassword); + return connectionFactory; + } + + @Bean + public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter()); + return rabbitTemplate; + } + + @Bean + public MessageConverter jackson2JsonMessageConverter() { + return new Jackson2JsonMessageConverter(); + } +}