-
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 1 commit
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,30 @@ | ||
package ranking; | ||
|
||
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; | ||
|
||
@RestController | ||
@RequestMapping("/main") | ||
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.
|
||
@RequiredArgsConstructor | ||
@Tag(name = "1. [메인 페이지]") | ||
public class MainController { | ||
private final RankingService rankingService; | ||
|
||
@Operation(summary = "일간/주간/월간 랭킹을 조회합니다.") | ||
@GetMapping("/ranking") | ||
public ResponseEntity<MainPageRankingInfoVo> getRanking(@RequestParam 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.
|
||
MainPageRankingInfoVo rankingInfo = rankingService.getRankingInfo(rankingType); | ||
|
||
if (rankingInfo == null) { | ||
return ResponseEntity.notFound().build(); | ||
} | ||
|
||
return ResponseEntity.ok(rankingInfo); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package ranking; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
import java.util.List; | ||
|
||
@Getter | ||
@Builder | ||
public class MainPageRankingInfoVo { | ||
private final RankingType rankingType; | ||
private final List<RankedPostVo> rankedPosts; | ||
|
||
public static MainPageRankingInfoVo from(Ranking ranking) { | ||
return MainPageRankingInfoVo.builder() | ||
.rankingType(ranking.getRankingType()) | ||
.rankedPosts(ranking.getRankedPosts().stream() | ||
.map(RankedPostVo::from) | ||
.toList()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package ranking; | ||
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.
|
||
|
||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.persistence.Table; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@Table(name = "tbl_ranked_post") | ||
@NoArgsConstructor | ||
public class RankedPost { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne | ||
@JoinColumn(name = "post_id") | ||
private Post post; | ||
// 랭킹에 속한 게시물 | ||
|
||
private int rank; | ||
// 게시물의 랭킹 순위 | ||
|
||
// 생성자, 게터, 세터 등 필요한 메서드 추가 | ||
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. Redis에서 사용을 안하면 당장은 굳이 필요 없을 것 같습니다 :) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package ranking; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@Builder | ||
public class RankedPostVo { | ||
private final Long postId; | ||
private final int rank; | ||
|
||
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 static RankedPostVo from(RankedPost rankedPost) { | ||
return RankedPostVo.builder() | ||
.postId(rankedPost.getPost().getId()) | ||
.rank(rankedPost.getRank()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package ranking; | ||
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. Domain 모듈에 넣어주면 좋을 것 같아요 :) |
||
|
||
import jakarta.persistence.CascadeType; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.OneToMany; | ||
import jakarta.persistence.Table; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Entity | ||
@Getter | ||
@Table(name = "tbl_ranking") | ||
@NoArgsConstructor | ||
public class Ranking { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Enumerated(EnumType.STRING) | ||
private RankingType rankingType; | ||
// 일간, 주간, 월간 중 하나를 나타냄 | ||
@OneToMany(mappedBy = "ranking", cascade = CascadeType.ALL) | ||
private List<RankedPost> rankedPosts = new ArrayList<>(); | ||
// 랭킹에 속한 게시물 목록 | ||
|
||
// 생성자, 게터, 세터 등 필요한 메서드를 추가 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package ranking; | ||
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. 패키지명! |
||
|
||
import lombok.RequiredArgsConstructor; | ||
import playlist.server.annotation.Adaptor; | ||
|
||
@Adaptor | ||
@RequiredArgsConstructor | ||
public class RankingAdaptor { | ||
private final RankingRepository rankingRepository; | ||
|
||
public Ranking queryByRankingType(RankingType rankingType) { | ||
return rankingRepository.findByRankingType(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.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package ranking; | ||
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.
|
||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface RankingRepository extends JpaRepository<Ranking, Long> { | ||
Ranking findByRankingType(RankingType rankingType); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,72 @@ | ||||||||||
package ranking; | ||||||||||
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. 패키지 구조는
Suggested change
로 하면 좋을 것 같습니다. |
||||||||||
|
||||||||||
import org.springframework.beans.factory.annotation.Autowired; | ||||||||||
import org.springframework.data.redis.core.RedisTemplate; | ||||||||||
import org.springframework.stereotype.Service; | ||||||||||
|
||||||||||
@Service | ||||||||||
public class RankingService { | ||||||||||
|
||||||||||
private final RedisTemplate<String, String> redisTemplate; | ||||||||||
|
||||||||||
@Autowired | ||||||||||
public RankingService(RedisTemplate<String, String> redisTemplate) { | ||||||||||
this.redisTemplate = redisTemplate; | ||||||||||
} | ||||||||||
|
||||||||||
// 좋아요증가, 랭킹 업데이트 (일간) | ||||||||||
public void incrementDailyLikes(String boardId) { | ||||||||||
redisTemplate.opsForHash().increment("like_daily_counts", boardId, 1L); | ||||||||||
redisTemplate.opsForZSet().add("like_daily_ranking", boardId, (double) getCurrentLikes("like_daily_counts", boardId)); | ||||||||||
} | ||||||||||
|
||||||||||
// // 좋아요증가, 랭킹 업데이트 (주간) | ||||||||||
public void incrementWeeklyLikes(String boardId) { | ||||||||||
redisTemplate.opsForHash().increment("like_weekly_counts", boardId, 1L); | ||||||||||
redisTemplate.opsForZSet().add("like_weekly_ranking", boardId, (double) getCurrentLikes("like_weekly_counts", boardId)); | ||||||||||
} | ||||||||||
|
||||||||||
// // 좋아요증가, 랭킹 업데이트 (월간) | ||||||||||
public void incrementMonthlyLikes(String boardId) { | ||||||||||
redisTemplate.opsForHash().increment("like_monthly_counts", boardId, 1L); | ||||||||||
redisTemplate.opsForZSet().add("like_monthly_ranking", boardId, (double) getCurrentLikes("like_monthly_counts", boardId)); | ||||||||||
} | ||||||||||
|
||||||||||
// 조회수 증가, 랭킹 업데이트 (일간) | ||||||||||
public void incrementDailyViews(String boardId) { | ||||||||||
redisTemplate.opsForHash().increment("view_daily_counts", boardId, 1L); | ||||||||||
redisTemplate.opsForZSet().add("view_daily_ranking", boardId, (double) getCurrentViews("view_daily_counts", boardId)); | ||||||||||
} | ||||||||||
|
||||||||||
// 조회수 증가, 랭킹 업데이트 (주간) | ||||||||||
public void incrementWeeklyViews(String boardId) { | ||||||||||
redisTemplate.opsForHash().increment("view_weekly_counts", boardId, 1L); | ||||||||||
redisTemplate.opsForZSet().add("view_weekly_ranking", boardId, (double) getCurrentViews("view_weekly_counts", boardId)); | ||||||||||
} | ||||||||||
|
||||||||||
// 조회수 증가, 랭킹 업데이트 (월간) | ||||||||||
public void incrementMonthlyViews(String boardId) { | ||||||||||
redisTemplate.opsForHash().increment("view_monthly_counts", boardId, 1L); | ||||||||||
redisTemplate.opsForZSet().add("view_monthly_ranking", boardId, (double) getCurrentViews("view_monthly_counts", boardId)); | ||||||||||
} | ||||||||||
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. View와 좋아요는 서로 Class로 기능을 분류하는게 좋을 것 같아요. |
||||||||||
|
||||||||||
// 현재 게시물의 좋아요를 가져오는 메서드 | ||||||||||
private long getCurrentLikes(String hash, String boardId) { | ||||||||||
Object likes = redisTemplate.opsForHash().get(hash, boardId); | ||||||||||
if (likes != null) { | ||||||||||
return (long) likes; | ||||||||||
} else { | ||||||||||
return 0L; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// 현재 게시물의 조회수를 가져오는 메서드 | ||||||||||
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,18 @@ | ||||||||||
package ranking; | ||||||||||
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.
Suggested change
|
||||||||||
|
||||||||||
public enum RankingType { | ||||||||||
DAILY("일간"), | ||||||||||
WEEKLY("주간"), | ||||||||||
MONTHLY("월간"); | ||||||||||
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. Enum사용 완전좋아요 |
||||||||||
|
||||||||||
private final String description; | ||||||||||
|
||||||||||
RankingType(String description) { | ||||||||||
this.description = description; | ||||||||||
} | ||||||||||
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 String getDescription() { | ||||||||||
return description; | ||||||||||
} | ||||||||||
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,15 @@ | ||
package search; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
import javax.naming.directory.SearchResult; | ||
import java.util.List; | ||
|
||
@Component | ||
public class SearchAdapter { | ||
|
||
public List<SearchResult> adaptSearchResults() { | ||
|
||
} | ||
} | ||
|
||
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,37 @@ | ||||||
package 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.
Suggested change
|
||||||
|
||||||
import org.springframework.beans.factory.annotation.Autowired; | ||||||
import org.springframework.http.ResponseEntity; | ||||||
import org.springframework.web.bind.annotation.*; | ||||||
|
||||||
import java.util.List; | ||||||
|
||||||
@RestController | ||||||
@RequestMapping("/search") | ||||||
public class SearchController { | ||||||
|
||||||
@Autowired | ||||||
private 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. Bean주입의 경우 |
||||||
@GetMapping("/tag") | ||||||
public ResponseEntity<List<SearchVo>> searchByTag(@RequestParam("tag") String tag) { | ||||||
List<SearchVo> searchResults = searchService.searchByTag(tag); | ||||||
|
||||||
if (searchResults.isEmpty()) { | ||||||
return ResponseEntity.notFound().build(); | ||||||
} | ||||||
|
||||||
return ResponseEntity.ok(searchResults); | ||||||
} | ||||||
|
||||||
@GetMapping("/autocomplete") | ||||||
public ResponseEntity<List<String>> autoComplete(@RequestParam("query") String query) { | ||||||
List<String> suggestions = searchService.autoComplete(query); | ||||||
|
||||||
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. 해당 기능의 경우 간단한 설명을 추가해주세요 (Swagger) |
||||||
if (suggestions.isEmpty()) { | ||||||
return ResponseEntity.notFound().build(); | ||||||
} | ||||||
|
||||||
return ResponseEntity.ok(suggestions); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package 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.
|
||
|
||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
|
||
@Entity | ||
public class SearchEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
private String title; | ||
private String description; | ||
private String tag; | ||
|
||
// Getter와 Setter 메서드 | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public void setId(Long id) { | ||
this.id = id; | ||
} | ||
|
||
public String getTitle() { | ||
return title; | ||
} | ||
|
||
public void setTitle(String title) { | ||
this.title = title; | ||
} | ||
|
||
public String getDescription() { | ||
return description; | ||
} | ||
|
||
public void setDescription(String description) { | ||
this.description = description; | ||
} | ||
|
||
public String getTag() { | ||
return tag; | ||
} | ||
|
||
public void setTag(String tag) { | ||
this.tag = 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.
|
||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package 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.
|
||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import java.util.List; | ||
|
||
@Repository | ||
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 interface SearchRepository extends JpaRepository<SearchEntity, Long> { | ||
|
||
// 태그를 기반으로 검색하기 위한 사용자 정의 메서드 | ||
List<SearchEntity> findByTag(String tag); | ||
|
||
// 자동완성을 위한 사용자 정의 메서드 | ||
List<SearchEntity> findByTitleStartingWith(String query); | ||
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. Tag또는 Title에서 기능을 다 가져올 수 있을 것 같습니다. |
||
} |
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.