Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: RSS Feed 가져오는 기능 구현 #22

Merged
merged 4 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,19 @@ task copyConfigSettings(type: Copy) {


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'com.rometools:rome:1.15.0'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.batch:spring-batch-test'
}

test {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/study/platform/PlatformApplication.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.study.platform;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableBatchProcessing
@SpringBootApplication
public class PlatformApplication {

public static void main(String[] args) {
public static void main(final String[] args) {
SpringApplication.run(PlatformApplication.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.study.platform.blog.batch.job;

import com.study.platform.blog.domain.Blog;
import com.study.platform.blog.domain.Feed;
import com.study.platform.blog.service.FeedReader;
import com.study.platform.blog.service.dto.FeedDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.database.JpaItemWriter;
import org.springframework.batch.item.database.JpaPagingItemReader;
import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManagerFactory;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@Configuration
public class FeedJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final EntityManagerFactory entityManagerFactory;
private final FeedReader feedReader;

@Bean
public Job feedJob() {
return jobBuilderFactory.get("feedJob")
.start(feedStop1())
.build();
}

@Bean
public Step feedStop1() {
return this.stepBuilderFactory.get("feedStep1")
.<Blog, List<Feed>>chunk(100)
.reader(feedItemReader())
.processor(feedItemProcessor())
.writer(feedItemWriter())
.build();
}

private JpaPagingItemReader<Blog> feedItemReader() {
return new JpaPagingItemReaderBuilder<Blog>()
.name("feedItemReader")
.entityManagerFactory(entityManagerFactory)
.pageSize(100)
.queryString("SELECT b FROM Blog b")
.build();
}

private ItemProcessor<Blog, List<Feed>> feedItemProcessor() {
return blog -> {
final List<FeedDto> feeds = feedReader.getFeeds(blog.getLink());

return feeds.stream()
.map(feedDto -> feedDto.toEntity(blog))
.collect(Collectors.toList());
};
}

private JpaItemListWriter<Feed> feedItemWriter() {
final JpaItemWriter<Feed> jpaItemWriter = new JpaItemWriter<>();
jpaItemWriter.setEntityManagerFactory(entityManagerFactory);

return new JpaItemListWriter<>(jpaItemWriter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.study.platform.blog.batch.job;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.item.database.JpaItemWriter;

import java.util.ArrayList;
import java.util.List;

@RequiredArgsConstructor
public class JpaItemListWriter<T> extends JpaItemWriter<List<T>> {
private final JpaItemWriter<T> jpaItemWriter;

@Override
public void write(final List<? extends List<T>> items) {
final List<T> list = new ArrayList<>();

for (final List<T> item : items) {
list.addAll(item);
}

this.jpaItemWriter.write(list);
}
}
20 changes: 7 additions & 13 deletions src/main/java/com/study/platform/blog/domain/Feed.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
package com.study.platform.blog.domain;

import java.net.URL;
import java.time.LocalDateTime;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.net.URL;
import java.util.Date;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
Expand All @@ -30,16 +23,17 @@ public class Feed {

private URL link;

@Lob
private String description;

@ManyToOne
@JoinColumn(name = "blog_id")
private Blog blog;

private LocalDateTime pubDate;
private Date pubDate;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocalDate 대신 Date를 쓴 이유가 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rss feed를 불러올때 DATE로 나와서 저렇게 변경해놨는데, 다음 작업 시 다시 한번 확인해보겠습니다!


@Builder
public Feed(Long id, String title, URL link, String description, Blog blog, LocalDateTime pubDate) {
public Feed(final Long id, final String title, final URL link, final String description, final Blog blog, final Date pubDate) {
this.id = id;
this.title = title;
this.link = link;
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/study/platform/blog/service/FeedReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.study.platform.blog.service;

import java.io.IOException;
import java.net.URL;
import java.util.List;

import org.springframework.stereotype.Service;

import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.io.FeedException;
import com.study.platform.blog.service.dto.FeedDto;

@Service
public interface FeedReader {
List<FeedDto> getFeeds(URL rssUrl) throws IOException, FeedException;

FeedDto getFeed(SyndEntry syndEntry) throws IOException, FeedException;
}
44 changes: 44 additions & 0 deletions src/main/java/com/study/platform/blog/service/FeedReaderImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.study.platform.blog.service;

import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;
import com.study.platform.blog.service.dto.FeedDto;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class FeedReaderImpl implements FeedReader {

@Override
public List<FeedDto> getFeeds(final URL rssUrl) throws IOException, FeedException {
final SyndFeedInput syndFeedInput = new SyndFeedInput();
final SyndFeed syndFeed = syndFeedInput.build(new XmlReader(rssUrl));

final List<SyndEntry> feeds = syndFeed.getEntries();

return feeds.stream()
.map(this::getFeed)
.collect(Collectors.toList());
}

@Override
public FeedDto getFeed(final SyndEntry syndEntry) {
try {
return FeedDto.builder()
.title(syndEntry.getTitle())
.link(new URL(syndEntry.getLink()))
.description(syndEntry.getDescription().getValue())
.pubDate(syndEntry.getPublishedDate())
.build();
} catch (final Exception e) {
throw new IllegalArgumentException("해당 feed를 찾을수 없습니다.");
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/study/platform/blog/service/dto/FeedDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.study.platform.blog.service.dto;

import com.study.platform.blog.domain.Blog;
import com.study.platform.blog.domain.Feed;
import lombok.Builder;
import lombok.Getter;

import java.net.URL;
import java.util.Date;

@Getter
public class FeedDto {
private final String title;

private final URL link;

private final String description;

private final Date pubDate;

@Builder
public FeedDto(final String title, final URL link, final String description, final Date pubDate) {
this.title = title;
this.link = link;
this.description = description;
this.pubDate = pubDate;
}

public Feed toEntity(final Blog blog) {
return Feed.builder()
.blog(blog)
.title(this.title)
.link(this.link)
.description(this.description)
.pubDate(this.pubDate)
.build();
}
}
6 changes: 3 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
spring:
profiles:
active: local
h2:
console:
enabled: true
include:
- oauth
- db
16 changes: 16 additions & 0 deletions src/test/java/com/study/platform/TestJobConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.study.platform;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@EnableBatchProcessing
@Configuration
public class TestJobConfiguration {

@Bean
public JobLauncherTestUtils jobLauncherTestUtils() {
return new JobLauncherTestUtils();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.study.platform.blog.batch.job;

import com.study.platform.blog.domain.Blog;
import com.study.platform.blog.domain.BlogRepository;
import com.study.platform.blog.domain.FeedRepository;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import java.net.URL;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

@SpringBootTest
@TestPropertySource(properties = {"job.name" + "feedJob"})
class FeedJobConfigurationTest {

@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;

@Autowired
private BlogRepository blogRepository;

@Autowired
private FeedRepository feedRepository;

@Test
void blogFeedBatch() throws Exception {
// given
this.blogRepository.save(Blog.builder()
.title("test")
.link(new URL("https://rutgo-letsgo.tistory.com/rss"))
.build());

final JobExecution jobExecution = jobLauncherTestUtils.launchJob();

assertAll(
() -> assertThat(jobExecution.getStatus()).isEqualTo(BatchStatus.COMPLETED),
() -> assertThat(feedRepository.findAll()).hasSize(50)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.study.platform.blog.service;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

import java.io.IOException;
import java.net.URL;
import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import com.rometools.rome.io.FeedException;
import com.study.platform.blog.service.dto.FeedDto;

class FeedReaderImplTest {

private final FeedReader feedReader = new FeedReaderImpl();

@DisplayName("BlogFeed 전체 가져오는 테스트")
@Test
void getFeeds() throws IOException, FeedException {
List<FeedDto> feeds = feedReader.getFeeds(new URL("https://rutgo-letsgo.tistory.com/rss"));

assertAll(
() -> assertThat(feeds).isNotEmpty()
);
}
}