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

[#31] 기간별 인기 피드 #32

Merged
merged 23 commits into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5263c13
Feat: 일, 주, 월별 인기 피드 업데이트
jzakka Feb 5, 2024
37fffa3
Fix: zset조회 로직 수정
jzakka Feb 5, 2024
38b4e53
Fix: deleteByMemberAndFeed가 id를 받도록 수정
jzakka Feb 5, 2024
0bd5a48
Refactor: feedId를 인자로 받도록 수정
jzakka Feb 5, 2024
e9810dd
Refactor: 랭킹정보를 RankingController에서 제공
jzakka Feb 6, 2024
ee59359
Style: 테스트이름 변경
jzakka Feb 6, 2024
5d632ce
Feat: 권한이 필요한 경우 서비스에서 PostAuthroize로 체크
jzakka Feb 4, 2024
a5d1fdc
Feat: 좋아요 기능
jzakka Feb 4, 2024
59e31df
Feat: 댓글, 대댓글 작성
jzakka Feb 4, 2024
9f412ce
Delete: FeedService에서 rankingRepository에 대한 의존성 제거
jzakka Feb 6, 2024
2bf9736
Feat: 배치작업 구성
jzakka Feb 8, 2024
e24aa60
Test: 통합테스트 한 테스트로 묶음
jzakka Feb 8, 2024
51a51d0
Test: RankingRepositoryTest 통합테스트로 묶음
jzakka Feb 8, 2024
f4d1144
Test: 테스트 추가
jzakka Feb 8, 2024
8f3ed12
Package: batch 설정을 config패키지 밖으로 뺌
jzakka Feb 9, 2024
c4bae8f
Refactor: CacheKey 목적에 따라 분리
jzakka Feb 9, 2024
124e3e0
Refactor: 배치 파일을 잡 목적에 따라 분리
jzakka Feb 9, 2024
082c0e2
Feat: 잡이 프로필설정에 따라 생성되도록 설정
jzakka Feb 9, 2024
2e77773
Fix: 인기 피드 정보를 한 쿼리로 가져오도록 수정
jzakka Feb 9, 2024
eef16d7
Fix: 잡 이름 수정
jzakka Feb 9, 2024
2c0e01d
Feat: 인기 피드는 삭제 못하게 설정
jzakka Feb 9, 2024
8d106bf
Feat: 인기피드 저장시, `id`, `작성자`, `여행지역`을 저장하도록 수정
jzakka Feb 10, 2024
3155b96
Style: 메서드 이름 수정
jzakka Feb 10, 2024
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
133 changes: 133 additions & 0 deletions src/main/java/com/stoury/batch/BatchHotFeedsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.stoury.batch;

import com.stoury.domain.Feed;
import com.stoury.dto.feed.SimpleFeedResponse;
import com.stoury.repository.LikeRepository;
import com.stoury.repository.RankingRepository;
import jakarta.persistence.EntityManagerFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.JpaPagingItemReader;
import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.util.Pair;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;

import java.time.temporal.ChronoUnit;

@Configuration
@RequiredArgsConstructor
@ConditionalOnExpression("'${spring.batch.job.names}'.contains('jobHotFeeds')")
public class BatchHotFeedsConfig {
private final EntityManagerFactory entityManagerFactory;

private final RankingRepository rankingRepository;

@Bean
public Job jobDailyFeed(JobRepository jobRepository, PlatformTransactionManager tm,
ThreadPoolTaskExecutor taskExecutor, LikeRepository likeRepository) {
return new JobBuilder("jobDailyFeed", jobRepository)
.start(stepDailyFeed(jobRepository, tm, taskExecutor, likeRepository))
.build();
}

@Bean
public Job jobWeeklyFeed(JobRepository jobRepository, PlatformTransactionManager tm,
ThreadPoolTaskExecutor taskExecutor, LikeRepository likeRepository) {
return new JobBuilder("jobWeeklyFeed", jobRepository)
.start(stepWeeklyFeed(jobRepository, tm, taskExecutor, likeRepository))
.build();
}

@Bean
public Job jobMonthlyFeed(JobRepository jobRepository, PlatformTransactionManager tm,
ThreadPoolTaskExecutor taskExecutor, LikeRepository likeRepository) {
return new JobBuilder("jobMonthlyFeed", jobRepository)
.start(stepMonthlyFeed(jobRepository, tm, taskExecutor, likeRepository))
.build();
}

@Bean
public Step stepDailyFeed(JobRepository jobRepository, PlatformTransactionManager tm,
ThreadPoolTaskExecutor taskExecutor, LikeRepository likeRepository) {
return new StepBuilder("stepDailyFeed", jobRepository)
.<Feed, Pair<SimpleFeedResponse, Long>>chunk(100, tm)
.reader(feedReader())
.processor(feedProcessor(likeRepository, ChronoUnit.DAYS))
.writer(feedWriter(ChronoUnit.DAYS))
.taskExecutor(taskExecutor)
.build();
}

@Bean
public Step stepWeeklyFeed(JobRepository jobRepository, PlatformTransactionManager tm,
ThreadPoolTaskExecutor taskExecutor, LikeRepository likeRepository) {
return new StepBuilder("stepWeeklyFeed", jobRepository)
.<Feed, Pair<SimpleFeedResponse, Long>>chunk(100, tm)
.reader(feedReader())
.processor(feedProcessor(likeRepository, ChronoUnit.WEEKS))
.writer(feedWriter(ChronoUnit.WEEKS))
.taskExecutor(taskExecutor)
.build();
}

@Bean
public Step stepMonthlyFeed(JobRepository jobRepository, PlatformTransactionManager tm,
ThreadPoolTaskExecutor taskExecutor, LikeRepository likeRepository) {
return new StepBuilder("stepMonthlyFeed", jobRepository)
.<Feed, Pair<SimpleFeedResponse, Long>>chunk(100, tm)
.reader(feedReader())
.processor(feedProcessor(likeRepository, ChronoUnit.MONTHS))
.writer(feedWriter(ChronoUnit.MONTHS))
.taskExecutor(taskExecutor)
.build();
}


public JpaPagingItemReader<Feed> feedReader() {
return new JpaPagingItemReaderBuilder<Feed>()
.name("feedReader")
.pageSize(100)
.entityManagerFactory(entityManagerFactory)
.queryString("select f from Feed f")
.build();
}

public ItemProcessor<Feed, Pair<SimpleFeedResponse, Long>> feedProcessor(LikeRepository likeRepository, ChronoUnit chronoUnit) {
return feed -> {
Long feedId = feed.getId();

if (!likeRepository.existsByFeedId(feedId.toString())) {
return null;
}
long currentLikes = likeRepository.getCountByFeedId(feedId.toString());
long prevLikes = likeRepository.getCountSnapshotByFeed(feedId.toString(), chronoUnit);

SimpleFeedResponse simpleFeed = SimpleFeedResponse.from(feed);

return Pair.of(simpleFeed, currentLikes - prevLikes);
};
}

public ItemWriter<Pair<SimpleFeedResponse, Long>> feedWriter(ChronoUnit chronoUnit) {
return list -> {
for (Pair<SimpleFeedResponse, Long> pair : list) {
SimpleFeedResponse rawSimpleFeed = pair.getFirst();
Long likeIncrease = pair.getSecond();

if (likeIncrease > 0) {
rankingRepository.saveHotFeed(rawSimpleFeed, likeIncrease, chronoUnit);
}
}
};
}
}
73 changes: 73 additions & 0 deletions src/main/java/com/stoury/batch/BatchPopularSpotsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.stoury.batch;

import com.stoury.repository.FeedRepository;
import com.stoury.repository.RankingRepository;
import com.stoury.utils.cachekeys.PopularSpotsKey;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;

import java.util.List;

@Configuration
@RequiredArgsConstructor
@ConditionalOnExpression("'${spring.batch.job.names}'.contains('jobPopularSpots')")
public class BatchPopularSpotsConfig {
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

Pageable pageable = PageRequest.of(0, 10);
private final FeedRepository feedRepository;
private final RankingRepository rankingRepository;


@Bean
public Step stepUpdatePopularDomesticCities(JobRepository jobRepository, PlatformTransactionManager tm) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

약간 사소한건데 Step이나 Job 같이 클래스가 맡고 있는 역할에 대한 정보는 이름 맨 뒤에 붙이는게 정석이에요..!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updatePopularDomesticCitiesStep 이렇게인가요?
나중에 수정하도록 하겠습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

넵 맞습니다!

return new StepBuilder("stepUpdatePopularDomesticCities", jobRepository)
.tasklet((contribution, chunkContext) -> {
List<String> rankedDomesticCities = feedRepository.findTop10CitiesInKorea(pageable);
rankingRepository.update(PopularSpotsKey.POPULAR_DOMESTIC_SPOTS, rankedDomesticCities);
return RepeatStatus.FINISHED;
}, tm)
.build();
}

@Bean
public Step stepUpdatePopularAbroadCities(JobRepository jobRepository, PlatformTransactionManager tm) {
return new StepBuilder("stepUpdatePopularAbroadCities", jobRepository)
.tasklet((contribution, chunkContext) -> {
List<String> rankedCountries = feedRepository.findTop10CountriesNotKorea(pageable);
rankingRepository.update(PopularSpotsKey.POPULAR_ABROAD_SPOTS, rankedCountries);
return RepeatStatus.FINISHED;
}, tm)
.build();
}

@Bean
public Job jobUpdatePopularSpots(JobRepository jobRepository, PlatformTransactionManager tm,
ThreadPoolTaskExecutor taskExecutor) {
Flow flowUpdatePopularAbroadCities = new FlowBuilder<Flow>("flowUpdatePopularAbroadCities")
.start(stepUpdatePopularAbroadCities(jobRepository, tm))
.build();
Flow flowUpdatePopularDomesticCities = new FlowBuilder<Flow>("flowUpdatePopularDomesticCities")
.start(stepUpdatePopularDomesticCities(jobRepository, tm))
.build();

return new JobBuilder("updatePopularSpots", jobRepository)
.start(flowUpdatePopularAbroadCities)
.split(taskExecutor)
.add(flowUpdatePopularDomesticCities)
.end()
.build();
}
}
Loading
Loading