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 14 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
139 changes: 139 additions & 0 deletions src/main/java/com/stoury/config/batch/BatchConfig.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.stoury.config.batch;

import com.stoury.domain.Feed;
import com.stoury.repository.FeedRepository;
import com.stoury.repository.LikeRepository;
import com.stoury.repository.RankingRepository;
import com.stoury.utils.CacheKeys;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceContext;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
Expand All @@ -11,15 +15,22 @@
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.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.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
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.data.util.Pair;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;

import java.time.temporal.ChronoUnit;
import java.util.List;

@Configuration
Expand All @@ -29,6 +40,7 @@ public class BatchConfig {
Pageable pageable = PageRequest.of(0, 10);
private final FeedRepository feedRepository;
private final RankingRepository rankingRepository;
private final EntityManagerFactory entityManagerFactory;

@Bean
public Step stepUpdatePopularDomesticCities(JobRepository jobRepository, PlatformTransactionManager tm) {
Expand Down Expand Up @@ -69,4 +81,131 @@ public Job jobUpdatePopularSpots(JobRepository jobRepository, PlatformTransactio
.end()
.build();
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

이 파일 패키지를 config 밑이 아니라 batch로 따로 빼는게 좋을 것 같아요 ~

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

@Bean
public ItemProcessor<Feed, Pair<String, Long>> dailyFeedProcessor(LikeRepository likeRepository) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

얘네 빈으로 사용되고 있지 않은 것 같은데, 그냥 feedProcessor 호출하도록 만들어도 되지 않을까요 ?
추가로, 이 집계에 대한 배치는 파일이 따로 분리되면 좋을 것 같습니다 ~

Copy link
Collaborator Author

@jzakka jzakka Feb 9, 2024

Choose a reason for hiding this comment

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

배치 설정 예시를 보면 아이템 리더,프로세서,라이터 하나하나를 빈으로 설정하길래 일단 따라했는데 그렇게 안해도 되는 거였군요!

Copy link
Collaborator

Choose a reason for hiding this comment

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

넵 빈으로 해도 되긴 하는데 지금은 빈 등록 후 빈으로 안쓰고 있는 것 같아서 말씀드렸어요 :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

배치는 보통 젠킨스같은곳에서 돌 때 해당 스케줄에 해당 잡만 수행되도록 구성하기 때문에 @ConditionalOnProperty 를 이용해서 본인의 job name으로 호출 될 경우에만 수행하도록 만들어 둡니다 ~

Copy link
Collaborator Author

@jzakka jzakka Feb 9, 2024

Choose a reason for hiding this comment

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

젠킨스에서 여러개의 인스턴스를 배포할 때 특정 잡을 돌리고 싶은 인스턴스만 지정해서 배포하면, 하나의 인스턴스만 그 잡을 수행하게 하는 그런 메커니즘이 맞나요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

음 .. 정확히는 하나의 인스턴스에 파일을 올려두고 .. 맞나 ? 인스턴스가 하난지는 잘 기억이 안나네요 워커가 여러개긴 한데 그게 별도의 인스턴스였는지 가물가물해서 ..

아무튼 배치 파일 배포는 jar 파일을 업로드 하는 걸 말하고, 모든 젠킨스 잡은 같은 파일을 사용하게 돼요. 그래서 파일 실행 시 java -jar application.jar --spring.batch.job.names=jobName 이런 느낌으로 이름을 줘서 실행시키고, 그 이름을 가진 잡만 로드되게 만들어요 ~

return feedProcessor(likeRepository, ChronoUnit.DAYS);
}

@Bean
public ItemProcessor<Feed, Pair<String, Long>> weeklyFeedProcessor(LikeRepository likeRepository) {
return feedProcessor(likeRepository, ChronoUnit.WEEKS);
}

@Bean
public ItemProcessor<Feed, Pair<String, Long>> monthlyFeedProcessor(LikeRepository likeRepository) {
return feedProcessor(likeRepository, ChronoUnit.MONTHS);
}

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

if (!likeRepository.existsByFeedId(feedId)) {
return Pair.of(feedId, 0L);
}
long currentLikes = likeRepository.getCountByFeedId(feedId);
long prevLikes = likeRepository.getCountSnapshotByFeed(feedId, chronoUnit);

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

@Bean
public ItemWriter<Pair<String, Long>> dailyFeedWriter() {
return feedWriter(ChronoUnit.DAYS);
}

@Bean
public ItemWriter<Pair<String, Long>> weeklyFeedWriter() {
return feedWriter(ChronoUnit.WEEKS);
}

@Bean
public ItemWriter<Pair<String, Long>> monthlyFeedWriter() {
return feedWriter(ChronoUnit.MONTHS);
}

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

if (likeIncrease > 0) {
rankingRepository.saveHotFeed(feedId, likeIncrease, chronoUnit);
}
}
};
}

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

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

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

@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();
}
}
36 changes: 0 additions & 36 deletions src/main/java/com/stoury/config/batch/BatchScheduler.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMapping
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/feeds/member/**")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/feeds/tag/**")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/feeds/popular/*")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/feeds/*")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/comments/**")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/rank/**")).permitAll()
.anyRequest().authenticated())
.exceptionHandling(exHandler -> exHandler
.authenticationEntryPoint((request, response, authException) -> response
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/stoury/controller/CommentController.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

import java.time.LocalDateTime;
import java.util.List;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
Expand Down
11 changes: 0 additions & 11 deletions src/main/java/com/stoury/controller/FeedController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

Expand Down Expand Up @@ -46,16 +45,6 @@ public List<FeedResponse> getFeedsOfMember(@PathVariable Long memberId,
return feedService.getFeedsOfMemberId(memberId, orderThan);
}

@GetMapping("/feeds/popular/abroad-spots")
public List<String> getPopularAbroadSpots() {
return feedService.getPopularAbroadSpots();
}

@GetMapping("/feeds/popular/domestic-spots")
public List<String> getPopularDomesticSpots() {
return feedService.getPopularDomesticSpots();
}

@PutMapping("/feeds/{feedId}")
public FeedResponse updateFeed(@PathVariable Long feedId, @RequestBody FeedUpdateRequest feedUpdateRequest) {
return feedService.updateFeed(feedId, feedUpdateRequest);
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/com/stoury/controller/RankingController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.stoury.controller;

import com.stoury.dto.feed.FeedResponse;
import com.stoury.service.RankingService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.temporal.ChronoUnit;
import java.util.List;

@RestController
@RequiredArgsConstructor
public class RankingController {
private final RankingService rankingService;

@GetMapping("/rank/abroad-spots")
public List<String> getPopularAbroadSpots() {
return rankingService.getPopularAbroadSpots();
}

@GetMapping("/rank/domestic-spots")
public List<String> getPopularDomesticSpots() {
return rankingService.getPopularDomesticSpots();
}

@GetMapping("/rank/daily-hot-feeds")
public List<FeedResponse> getDailyHotFeeds() {
return rankingService.getHotFeeds(ChronoUnit.DAYS);
}

@GetMapping("/rank/weekly-hot-feeds")
public List<FeedResponse> getWeeklyHotFeeds() {
return rankingService.getHotFeeds(ChronoUnit.WEEKS);
}

@GetMapping("/rank/monthly-hot-feeds")
public List<FeedResponse> getMonthlyHotFeeds() {
return rankingService.getHotFeeds(ChronoUnit.MONTHS);
}
}
6 changes: 4 additions & 2 deletions src/main/java/com/stoury/domain/Feed.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class Feed {
@CreatedDate
private LocalDateTime createdAt;

@JoinTable(joinColumns = @JoinColumn(name = "FEED_ID"))
@JoinColumn(name = "FEED_ID")
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<GraphicContent> graphicContents = new ArrayList<>();

Expand All @@ -45,7 +45,9 @@ public class Feed {
private Double longitude;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(joinColumns = @JoinColumn(name = "FEED_ID"),
@JoinTable(
name = "FEED_TAG",
joinColumns = @JoinColumn(name = "FEED_ID"),
inverseJoinColumns = @JoinColumn(name = "TAG_ID"))
private List<Tag> tags = new ArrayList<>();

Expand Down
Loading
Loading