-
Notifications
You must be signed in to change notification settings - Fork 0
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 : redis 랭킹, 검색 구현 #31
base: dev
Are you sure you want to change the base?
Changes from 2 commits
fa455af
d8b812c
cbb7f7b
d922e1b
ae6df9d
ac8c530
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package playlist.server.ranking; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; | ||
import playlist.server.domain.domains.ranking.domain.RankingType; | ||
|
||
@Getter | ||
@Builder | ||
public class MainPageRankingInfoVo { | ||
private final RankingInfo rankingInfo; | ||
private final RankingType rankingType; | ||
|
||
public static MainPageRankingInfoVo from(RankingInfo rankingInfo, RankingType rankingType) { | ||
return MainPageRankingInfoVo.builder() | ||
.rankingInfo(rankingInfo) | ||
.rankingType(rankingType) | ||
.build(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 빌더의 활용 정말 잘했습니당. 주의할점을 말해주자면, Annotation이 편하긴 한데 완벽은 없더라구요,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 확인했습니다!! 감사해요 증말!! 하트 뿅뿅 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package playlist.server.ranking.controller; | ||
|
||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import playlist.server.domain.domains.ranking.domain.RankingType; | ||
import playlist.server.ranking.MainPageRankingInfoVo; | ||
import playlist.server.ranking.service.RankingLikeService; | ||
import playlist.server.ranking.service.RankingViewService; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; // RankingInfo enum 추가 | ||
|
||
@RestController | ||
@RequestMapping("/ranking") | ||
@RequiredArgsConstructor | ||
@Tag(name = "1. [랭킹]") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 태그로 어울리는 정보를 전달하는거 매우 좋아보입니다 ^^ |
||
public class RankingController { | ||
|
||
private final RankingLikeService rankingLikeService; | ||
private final RankingViewService rankingViewService; | ||
|
||
@Operation(summary = "일간 랭킹을 조회합니다.") | ||
@GetMapping("/daily") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mapping을 차라리
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. type 매개변수 사용해서 type값에 따라 좋아요랑 조회수 랭킹 정보 가져오는 로직 만들어 봤습니다! |
||
public ResponseEntity<MainPageRankingInfoVo> getDailyRanking( | ||
@RequestParam(name = "rankingType", required = false, defaultValue = "DAILY") RankingType rankingType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 메소드를 daily, week, month로 분류를 하였기 때문에, 해당 파라미터가 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호라!!! 수정했습니다 |
||
RankingInfo rankingInfo = RankingInfo.DAILY; | ||
rankingLikeService.incrementLikes(rankingType.name(), rankingInfo); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기에서 타입에 따라 로직을 다르게 태우도록 하면 어떘을까 싶네요. |
||
MainPageRankingInfoVo rankingInfoVo = MainPageRankingInfoVo.from(rankingInfo, rankingType); | ||
|
||
if (rankingInfoVo == null) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이미 위에서 객체를 생성해 받고 있는데, If문이 왜 필요한지를 모르겠네요 또한 오류에 대해서 표기하고싶으면 NotFound를 직접적으로 사용하는 것보단 Exception을 사용해 보면 좋을 것 같습니다. 마지막으로, 이건 제 의견인데, Controller에서의 역할은 사전에 데이터를 검수하고, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 한번 쪼개봤습니다... 맞는지 확인 한 번 부탁드립니다 |
||
|
||
return ResponseEntity.ok(rankingInfoVo); | ||
} | ||
|
||
@Operation(summary = "주간 랭킹을 조회합니다.") | ||
@GetMapping("/weekly") | ||
public ResponseEntity<MainPageRankingInfoVo> getWeeklyRanking( | ||
@RequestParam(name = "rankingType", required = false, defaultValue = "WEEKLY") RankingType rankingType) { | ||
RankingInfo rankingInfo = RankingInfo.WEEKLY; | ||
rankingLikeService.incrementLikes(rankingType.name(), rankingInfo); | ||
MainPageRankingInfoVo rankingInfoVo = MainPageRankingInfoVo.from(rankingInfo, rankingType); | ||
|
||
if (rankingInfoVo == null) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
|
||
return ResponseEntity.ok(rankingInfoVo); | ||
} | ||
|
||
@Operation(summary = "월간 랭킹을 조회합니다.") | ||
@GetMapping("/monthly") | ||
public ResponseEntity<MainPageRankingInfoVo> getMonthlyRanking( | ||
@RequestParam(name = "rankingType", required = false, defaultValue = "MONTHLY") RankingType rankingType) { | ||
RankingInfo rankingInfo = RankingInfo.MONTHLY; | ||
rankingLikeService.incrementLikes(rankingType.name(), rankingInfo); | ||
MainPageRankingInfoVo rankingInfoVo = MainPageRankingInfoVo.from(rankingInfo, rankingType); | ||
|
||
if (rankingInfoVo == null) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
|
||
return ResponseEntity.ok(rankingInfoVo); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
@Operation(summary = "일간 조회수 랭킹을 조회합니다.") | ||
@GetMapping("/daily-views") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 Mapping이 꼭 필요한지 의문이네요. |
||
public ResponseEntity<MainPageRankingInfoVo> getDailyViewsRanking( | ||
@RequestParam(name = "rankingType", required = false, defaultValue = "DAILY") RankingType rankingType) { | ||
RankingInfo rankingInfo = RankingInfo.DAILY; | ||
rankingViewService.incrementViews(rankingType.name(), rankingInfo); | ||
MainPageRankingInfoVo rankingInfoVo = MainPageRankingInfoVo.from(rankingInfo, rankingType); | ||
|
||
if (rankingInfoVo == null) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
|
||
return ResponseEntity.ok(rankingInfoVo); | ||
} | ||
|
||
@Operation(summary = "주간 조회수 랭킹을 조회합니다.") | ||
@GetMapping("/weekly-views") | ||
public ResponseEntity<MainPageRankingInfoVo> getWeeklyViewsRanking( | ||
@RequestParam(name = "rankingType", required = false, defaultValue = "WEEKLY") RankingType rankingType) { | ||
RankingInfo rankingInfo = RankingInfo.WEEKLY; | ||
rankingViewService.incrementViews(rankingType.name(), rankingInfo); | ||
MainPageRankingInfoVo rankingInfoVo = MainPageRankingInfoVo.from(rankingInfo, rankingType); | ||
|
||
if (rankingInfoVo == null) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
|
||
return ResponseEntity.ok(rankingInfoVo); | ||
} | ||
|
||
@Operation(summary = "월간 조회수 랭킹을 조회합니다.") | ||
@GetMapping("/monthly-views") | ||
public ResponseEntity<MainPageRankingInfoVo> getMonthlyViewsRanking( | ||
@RequestParam(name = "rankingType", required = false, defaultValue = "MONTHLY") RankingType rankingType) { | ||
RankingInfo rankingInfo = RankingInfo.MONTHLY; | ||
rankingViewService.incrementViews(rankingType.name(), rankingInfo); | ||
MainPageRankingInfoVo rankingInfoVo = MainPageRankingInfoVo.from(rankingInfo, rankingType); | ||
|
||
if (rankingInfoVo == null) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
|
||
return ResponseEntity.ok(rankingInfoVo); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package playlist.server.ranking.service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import playlist.server.annotation.Adaptor; | ||
import playlist.server.domain.domains.ranking.domain.Ranking; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; | ||
import playlist.server.domain.domains.ranking.repository.RankingRepository; | ||
|
||
@Adaptor | ||
@RequiredArgsConstructor | ||
public class RankingAdaptor { | ||
private final RankingRepository rankingRepository; | ||
|
||
public Ranking queryByRankingInfo(RankingInfo rankingInfo) { | ||
return rankingRepository.findByRankingInfo(rankingInfo); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 Adaptor는 실사용은 안하는것 같은데, 삭제해도 괜찮지 않나요? 또한 레이어 구조가 Controller-Service-Repositry 이기 때문에 불필요해 보입니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 삭제했습니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package playlist.server.ranking.service; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.stereotype.Service; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; // RankingInfo enum 추가 | ||
|
||
@Service | ||
public class RankingLikeService { | ||
|
||
private final RedisTemplate<String, String> redisTemplate; | ||
|
||
@Autowired | ||
public RankingLikeService(RedisTemplate<String, String> redisTemplate) { | ||
this.redisTemplate = redisTemplate; | ||
} | ||
|
||
// 좋아요 증가, 랭킹 업데이트 (일간) | ||
public void incrementLikes(String boardId, RankingInfo rankingInfo) { | ||
redisTemplate.opsForHash().increment(rankingInfo.getCountsKey(), boardId, 1L); | ||
redisTemplate.opsForZSet().add(rankingInfo.getRankingKey(), boardId, (double) getCurrentLikes(rankingInfo.getCountsKey(), boardId)); | ||
} | ||
|
||
// 현재 게시물의 좋아요를 가져오는 메서드 | ||
private long getCurrentLikes(String hash, String boardId) { | ||
Object likes = redisTemplate.opsForHash().get(hash, boardId); | ||
if (likes != null) { | ||
return (long) likes; | ||
} else { | ||
return 0L; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package playlist.server.ranking.service; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.stereotype.Service; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; // RankingInfo enum 추가 | ||
|
||
@Service | ||
public class RankingViewService { | ||
|
||
private final RedisTemplate<String, String> redisTemplate; | ||
|
||
@Autowired | ||
public RankingViewService(RedisTemplate<String, String> redisTemplate) { | ||
this.redisTemplate = redisTemplate; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정했어요! |
||
|
||
// 조회수 증가, 랭킹 업데이트 (일간) | ||
public void incrementViews(String boardId, RankingInfo rankingInfo) { | ||
redisTemplate.opsForHash().increment(rankingInfo.getCountsKey(), boardId, 1L); | ||
redisTemplate.opsForZSet().add(rankingInfo.getRankingKey(), boardId, (double) getCurrentViews(rankingInfo.getCountsKey(), boardId)); | ||
} | ||
|
||
// 현재 게시물의 조회수를 가져오는 메서드 | ||
private long getCurrentViews(String hash, String boardId) { | ||
Object views = redisTemplate.opsForHash().get(hash, boardId); | ||
if (views != null) { | ||
return (long) views; | ||
} else { | ||
return 0L; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package playlist.server.search.controller; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import playlist.server.search.service.SearchService; | ||
import playlist.server.search.vo.SearchVo; | ||
|
||
import java.util.List; | ||
|
||
@RestController | ||
@RequestMapping("/playlist/server/search") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 생각했을때는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정했습니다! |
||
public class SearchController { | ||
|
||
private final SearchService searchService; | ||
|
||
public SearchController(SearchService searchService) { | ||
this.searchService = searchService; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// 태그 기반으로 검색 | ||
@GetMapping("/tag") | ||
public ResponseEntity<List<SearchVo>> searchByTag(@RequestParam("tag") String tag) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ResponseEntity로 제작하는건 Controller에서 하는게 좋아보이네요. |
||
List<SearchVo> searchResults = searchService.searchByTag(tag); | ||
|
||
if (searchResults.isEmpty()) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NotFound를 주기보단 Exception, 또는 Size가 0인 List를 반환하는것도 괜찮지 않을까요? notFound를 직접 선언한 이유가 궁금하네요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exception 으로 수정했고 @requiredargsconstructor 사용했습니다!! |
||
|
||
return ResponseEntity.ok(searchResults); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package playlist.server.search.service; | ||
|
||
import org.springframework.stereotype.Service; | ||
import playlist.server.domain.domains.search.domain.Search; | ||
import playlist.server.domain.domains.search.repository.SearchRepository; | ||
import playlist.server.search.vo.SearchVo; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
public class SearchService { | ||
|
||
private final SearchRepository searchRepository; | ||
|
||
public SearchService(SearchRepository searchRepository) { | ||
this.searchRepository = searchRepository; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정했슴둥 |
||
|
||
public List<SearchVo> searchByTag(String tag) { | ||
// 태그를 기반으로 검색 결과를 데이터베이스에서 조회 | ||
List<Search> searchEntities = searchRepository.findByTag(tag); | ||
|
||
// 검색 결과를 SearchVo로 변환 | ||
List<SearchVo> searchResults = searchEntities.stream() | ||
.map(search -> new SearchVo(search.getTitle(), search.getDescription())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
.collect(Collectors.toList()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lambda 멋져요 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헤헤헤헤히히히히히 |
||
|
||
return searchResults; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. return searchEntities.stream()
.map(search -> new SearchVo(search.getTitle(), search.getDescription()))
.collect(Collectors.toList()); 로 해보는게 어떨까요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정했어요! |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package playlist.server.search.vo; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.Setter; | ||
import lombok.ToString; | ||
|
||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
@ToString | ||
@Getter | ||
@Setter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 활용 완료! |
||
public class SearchVo { | ||
|
||
private String title; | ||
private String description; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package playlist.server.domain.domains.ranking.domain; | ||
|
||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
|
||
import jakarta.persistence.Table; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@Table(name = "tbl_ranking") | ||
@NoArgsConstructor | ||
public class Ranking { | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Enumerated(EnumType.STRING) | ||
private RankingType rankingType; | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package playlist.server.domain.domains.ranking.domain; | ||
|
||
public enum RankingInfo { | ||
DAILY("like_daily_counts", "like_daily_ranking"), | ||
WEEKLY("like_weekly_counts", "like_weekly_ranking"), | ||
MONTHLY("like_monthly_counts", "like_monthly_ranking"); | ||
|
||
private final String countsKey; | ||
private final String rankingKey; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 변수명 직관적인거 보기 좋네요 |
||
|
||
RankingInfo(String countsKey, String rankingKey) { | ||
this.countsKey = countsKey; | ||
this.rankingKey = rankingKey; | ||
} | ||
|
||
public String getCountsKey() { | ||
return countsKey; | ||
} | ||
|
||
public String getRankingKey() { | ||
return rankingKey; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정했습니다! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package playlist.server.domain.domains.ranking.domain; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public enum RankingType { | ||
DAILY("일간"), | ||
WEEKLY("주간"), | ||
MONTHLY("월간"); | ||
|
||
private final String description; | ||
|
||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package playlist.server.domain.domains.ranking.repository; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import playlist.server.domain.domains.ranking.domain.Ranking; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; | ||
|
||
|
||
public interface RankingRepository extends JpaRepository<Ranking, Long> { | ||
Ranking findByRankingInfo(RankingInfo rankingInfo); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
search의 경우
vo
패키지에 클래스를 정리한거 같아 보이는데, 해당 패이지는 그냥 ranking패키지 root경로에 vo를 생성한 이유가 있을까요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
\MainPageRankingInfo 이게 너무 네이밍이 구린거 같아 고민하다 놓친거 같습니다! 패키지 만들어서 vo 에 넣어뒀습니다!