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 : redis 랭킹, 검색 구현 #31

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions Api/src/main/java/ranking/MainController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ranking;
Copy link
Member

Choose a reason for hiding this comment

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

  • 기능별로 패키지를 분류해 주세요.
  • 패키지 위치도 잘못되었습니다!

Controller의 경우에는 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;

@RestController
@RequestMapping("/main")
Copy link
Member

Choose a reason for hiding this comment

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

main이라는 Mapping Value는 부적합한것 같아요. Main페이지에서도 컴포넌트의 분류를 나눌 수 있을 것 같습니다.

@RequiredArgsConstructor
@Tag(name = "1. [메인 페이지]")
public class MainController {
private final RankingService rankingService;

@Operation(summary = "일간/주간/월간 랭킹을 조회합니다.")
@GetMapping("/ranking")
public ResponseEntity<MainPageRankingInfoVo> getRanking(@RequestParam RankingType rankingType) {
Copy link
Member

Choose a reason for hiding this comment

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

@RequestParam 으로 기본값의 설정이 없으면 어떻게 될까요?
예를 들어 그냥 /ranking 으로만 요청을 보내는 경우 어떻게 될까요?

MainPageRankingInfoVo rankingInfo = rankingService.getRankingInfo(rankingType);

if (rankingInfo == null) {
return ResponseEntity.notFound().build();
}

return ResponseEntity.ok(rankingInfo);
}
}
22 changes: 22 additions & 0 deletions Api/src/main/java/ranking/MainPageRankingInfoVo.java
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();
}
}
31 changes: 31 additions & 0 deletions Api/src/main/java/ranking/RankedPost.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ranking;
Copy link
Member

Choose a reason for hiding this comment

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

Entity 의 경우에는 Domain에서 생성해 주세요 :)


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;
// 게시물의 랭킹 순위

// 생성자, 게터, 세터 등 필요한 메서드 추가
Copy link
Member

Choose a reason for hiding this comment

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

Redis에서 사용을 안하면 당장은 굳이 필요 없을 것 같습니다 :)

}
18 changes: 18 additions & 0 deletions Api/src/main/java/ranking/RankedPostVo.java
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;

Copy link
Member

Choose a reason for hiding this comment

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

현재 ArticleEntity에 대해서 생성을 완료 하였기 떄문에 해당 Entity를 같이 넣는 방식으로 VO를 작성하면 좋을 것 같습니다.

public static RankedPostVo from(RankedPost rankedPost) {
return RankedPostVo.builder()
.postId(rankedPost.getPost().getId())
.rank(rankedPost.getRank())
.build();
}
}
35 changes: 35 additions & 0 deletions Api/src/main/java/ranking/Ranking.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ranking;
Copy link
Member

Choose a reason for hiding this comment

The 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<>();
// 랭킹에 속한 게시물 목록

// 생성자, 게터, 세터 등 필요한 메서드를 추가
}
14 changes: 14 additions & 0 deletions Api/src/main/java/ranking/RankingAdaptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ranking;
Copy link
Member

Choose a reason for hiding this comment

The 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);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Adapter대신 Service구조로 변경해주세요 !

7 changes: 7 additions & 0 deletions Api/src/main/java/ranking/RankingRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ranking;
Copy link
Member

Choose a reason for hiding this comment

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

Repository의 경우에는 Domain모듈에 부탁드립니다.


import org.springframework.data.jpa.repository.JpaRepository;

public interface RankingRepository extends JpaRepository<Ranking, Long> {
Ranking findByRankingType(RankingType rankingType);
}
72 changes: 72 additions & 0 deletions Api/src/main/java/ranking/RankingService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ranking;
Copy link
Member

Choose a reason for hiding this comment

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

패키지 구조는

Suggested change
package ranking;
package playlist.server.ranking.service;
or
package playlist.server.rank.service;

로 하면 좋을 것 같습니다.


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));
}
Copy link
Member

Choose a reason for hiding this comment

The 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;
}
}
}
18 changes: 18 additions & 0 deletions Api/src/main/java/ranking/RankingType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ranking;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
package ranking;
package playlist.ranking.type;
or
package playlist.rank.type;


public enum RankingType {
DAILY("일간"),
WEEKLY("주간"),
MONTHLY("월간");
Copy link
Member

Choose a reason for hiding this comment

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

Enum사용 완전좋아요


private final String description;

RankingType(String description) {
this.description = description;
}
Copy link
Member

Choose a reason for hiding this comment

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

@AllArgsConstructor


public String getDescription() {
return description;
}
Copy link
Member

Choose a reason for hiding this comment

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

@Getter

}

15 changes: 15 additions & 0 deletions Api/src/main/java/search/SearchAdapter.java
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() {

}
}

Copy link
Member

Choose a reason for hiding this comment

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

service레이어로 바꾸기로..!
그리고 당장 필요 없으면 지워도 괜찮을 것 같습니다.

37 changes: 37 additions & 0 deletions Api/src/main/java/search/SearchController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package search;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
package search;
package playlist.server.search.service;


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;

Copy link
Member

Choose a reason for hiding this comment

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

Bean주입의 경우 Spring에서 권장하는 방식을 찾아보면 좋을 것 같아요.

@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);

Copy link
Member

Choose a reason for hiding this comment

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

해당 기능의 경우 간단한 설명을 추가해주세요 (Swagger)

if (suggestions.isEmpty()) {
return ResponseEntity.notFound().build();
}

return ResponseEntity.ok(suggestions);
}
}
52 changes: 52 additions & 0 deletions Api/src/main/java/search/SearchEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package search;
Copy link
Member

Choose a reason for hiding this comment

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

  • Entity의 경우에는 domain모듈에 넣어주세요.
  • Search기능의 경우 Article을 가져오는게 주된 기능이기 떄문에 Article에서 Entity에 대한 추가가 완료되면 작업이 가능할 것 같습니다.


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;
}
Copy link
Member

Choose a reason for hiding this comment

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

GetterSetter 메소드는 필요없습니다.
Lombok@Getter, @Setter, @Data에 대해서 알아보고 사용해보세요.

}

16 changes: 16 additions & 0 deletions Api/src/main/java/search/SearchRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package search;
Copy link
Member

Choose a reason for hiding this comment

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

Repository의 경우에는 Domain에서 생성을 해야 합니다.


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
Copy link
Member

Choose a reason for hiding this comment

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

JPA를 활용하는 경우에는 @Repository어노테이션이 꼭 필요하지는 않습니다.

public interface SearchRepository extends JpaRepository<SearchEntity, Long> {

// 태그를 기반으로 검색하기 위한 사용자 정의 메서드
List<SearchEntity> findByTag(String tag);

// 자동완성을 위한 사용자 정의 메서드
List<SearchEntity> findByTitleStartingWith(String query);
Copy link
Member

Choose a reason for hiding this comment

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

Tag또는 Title에서 기능을 다 가져올 수 있을 것 같습니다.

}
Loading
Loading