-
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 all 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,25 @@ | ||
package playlist.server.mainpagerankingInfo.vo; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; | ||
import playlist.server.domain.domains.ranking.domain.RankingType; | ||
|
||
import java.util.List; // 추가 | ||
|
||
@Getter | ||
@Builder | ||
public class MainPageRankingInfoVo { | ||
private final RankingInfo rankingInfo; | ||
private final RankingType rankingType; | ||
private final List<RankingInfo> rankingInfoList; | ||
|
||
|
||
public static MainPageRankingInfoVo from(RankingInfo rankingInfo, RankingType rankingType, List<RankingInfo> rankingInfoList) { | ||
return MainPageRankingInfoVo.builder() | ||
.rankingInfo(rankingInfo) | ||
.rankingType(rankingType) | ||
.rankingInfoList(rankingInfoList) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
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.RankingInfo; | ||
import playlist.server.domain.domains.ranking.domain.RankingType; | ||
import playlist.server.ranking.service.RankingService; | ||
import playlist.server.ranking.vo.ResponseRankingDto; | ||
|
||
import java.util.List; | ||
|
||
import static playlist.server.domain.domains.ranking.domain.RankingInfo.MONTH; | ||
import static playlist.server.domain.domains.ranking.domain.RankingInfo.WEEK; | ||
import static playlist.server.domain.domains.ranking.domain.RankingType.isStringLikeOrView; | ||
|
||
@RestController | ||
@RequestMapping("/ranking") | ||
@RequiredArgsConstructor | ||
@Tag(name = "1. [랭킹]") | ||
public class RankingController { | ||
|
||
private final RankingService rankingService; | ||
|
||
@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<List<ResponseRankingDto>> getDailyRanking( | ||
@RequestParam(name = "type", defaultValue = "like") String type) { | ||
return ResponseEntity.ok(getRanking(RankingInfo.DAILY, isStringLikeOrView(type))); | ||
} | ||
|
||
@Operation(summary = "주간 랭킹을 조회합니다.") | ||
@GetMapping("/weekly") | ||
public ResponseEntity<List<ResponseRankingDto>> getWeeklyRanking( | ||
@RequestParam(name = "type", defaultValue = "like") String type) { | ||
return ResponseEntity.ok(getRanking(WEEK, isStringLikeOrView(type))); | ||
} | ||
|
||
|
||
@Operation(summary = "월간 랭킹을 조회합니다.") | ||
@GetMapping("/monthly") | ||
public ResponseEntity<List<ResponseRankingDto>> getMonthlyRanking( | ||
@RequestParam(name = "type", defaultValue = "like") String type) { | ||
return ResponseEntity.ok(getRanking(MONTH, isStringLikeOrView(type))); | ||
} | ||
|
||
|
||
private List<ResponseRankingDto> getRanking(RankingInfo period, RankingType searchType) { | ||
return rankingService.getRankingList(period, searchType); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package playlist.server.ranking.service; | ||
|
||
import playlist.server.domain.domains.ranking.domain.RankingInfo; | ||
import playlist.server.domain.domains.ranking.domain.RankingType; | ||
import playlist.server.ranking.vo.ResponseRankingDto; | ||
|
||
import java.util.List; | ||
|
||
public interface RankingService { | ||
|
||
List<ResponseRankingDto> getRankingList(RankingInfo period, RankingType type); | ||
|
||
void incrementCountProcess(RankingType type, String boardId); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package playlist.server.ranking.service; | ||
|
||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.stereotype.Service; | ||
import playlist.server.domain.domains.ranking.domain.RankingInfo; | ||
import playlist.server.domain.domains.ranking.domain.RankingType; | ||
import playlist.server.ranking.vo.ResponseRankingDto; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
/** | ||
* 참고한 Reference : https://cantcoding.tistory.com/82 | ||
*/ | ||
@Service | ||
@RequiredArgsConstructor | ||
public class RedisRankingService implements RankingService { | ||
|
||
private final RedisTemplate<String, String> redisTemplate; | ||
|
||
/** | ||
* 랭킹 리스트를 0~10까지 가져온다. | ||
* @param period | ||
* @param type | ||
* @return | ||
*/ | ||
@Override | ||
public List<ResponseRankingDto> getRankingList(RankingInfo period, RankingType type) { | ||
return redisTemplate.opsForZSet() | ||
.reverseRangeWithScores(getRankingTypeKey(period, type), 0, 10) | ||
.stream() | ||
.map(ResponseRankingDto::convertToResponseRankingDto).toList(); | ||
} | ||
|
||
/** | ||
* Like, View의 Daily, Week, Month의 카운트를 모두 증가시킨다. | ||
* | ||
* @param type | ||
* @param boardId | ||
*/ | ||
@Override | ||
public void incrementCountProcess(RankingType type, String boardId) { | ||
Arrays.stream(RankingInfo.values()).forEach(period -> { | ||
if (isExistInRanking(period, type, boardId)) { | ||
incrementRankingCount(period, type, boardId); | ||
} else { | ||
addRanking(period, type, boardId); | ||
} | ||
}); | ||
} | ||
|
||
|
||
/** | ||
* 현재 ZSet에 값이 존재하는지 확인한다. | ||
* @param period | ||
* @param type | ||
* @param boardId | ||
* @return | ||
*/ | ||
private boolean isExistInRanking(RankingInfo period, RankingType type, String boardId) { | ||
return redisTemplate.opsForZSet() | ||
.score(getRankingTypeKey(period, type), boardId) != null; | ||
} | ||
|
||
/** | ||
* 값이 존재하지 않는 경우 Default Value로 1을 제공한다. | ||
* | ||
* @param period | ||
* @param type | ||
* @param boardId | ||
*/ | ||
private void addRanking(RankingInfo period, RankingType type, String boardId) { | ||
redisTemplate.opsForZSet().add(getRankingTypeKey(period, type), boardId, 1); | ||
} | ||
|
||
/** | ||
* Zset에 존재하는 경우 해당 값을 증가시킨다 | ||
* | ||
* @param period | ||
* @param type | ||
* @param boardId | ||
*/ | ||
private void incrementRankingCount(RankingInfo period, RankingType type, String boardId) { | ||
redisTemplate.opsForZSet() | ||
.incrementScore(getRankingTypeKey(period, type), boardId, 1); | ||
} | ||
|
||
/** | ||
* Type에 따라 Key를 다르게 가져온다. | ||
* | ||
* @param period | ||
* @param type | ||
* @return | ||
*/ | ||
private String getRankingTypeKey(RankingInfo period, RankingType type) { | ||
return RankingType.LIKE.equals(type) ? period.getLikeKey() : period.getViewKey(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package playlist.server.ranking.vo; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.Setter; | ||
import org.springframework.data.redis.core.ZSetOperations; | ||
|
||
@Getter | ||
@Setter | ||
@Builder | ||
@AllArgsConstructor | ||
@NoArgsConstructor | ||
public class ResponseRankingDto { | ||
private String boardId; | ||
private Long score; | ||
|
||
public static ResponseRankingDto convertToResponseRankingDto(ZSetOperations.TypedTuple typedTuple) { | ||
return ResponseRankingDto.builder() | ||
.boardId(typedTuple.getValue().toString()) | ||
.score(typedTuple.getScore().longValue()) | ||
.build(); | ||
} | ||
|
||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package playlist.server.search.controller; | ||
|
||
|
||
import java.util.List; | ||
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.exception.TagNotFoundException; | ||
import playlist.server.search.service.SearchService; | ||
import playlist.server.search.vo.SearchVo; | ||
|
||
@RestController | ||
@RequestMapping("/search") | ||
@RequiredArgsConstructor | ||
public class SearchController { | ||
|
||
private final SearchService searchService; | ||
|
||
// 태그 기반으로 검색 | ||
@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()) { | ||
throw TagNotFoundException.EXCEPTION; | ||
} | ||
|
||
return ResponseEntity.ok(searchResults); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package playlist.server.search.service; | ||
|
||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import playlist.server.domain.domains.search.repository.SearchRepository; | ||
import playlist.server.search.vo.SearchVo; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class SearchService { | ||
|
||
private final SearchRepository searchRepository; | ||
|
||
public List<SearchVo> searchByTag(String tag) { | ||
return searchRepository.findByTag(tag).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()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package playlist.server.search.vo; | ||
|
||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Data | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class SearchVo { | ||
private String title; | ||
private String description; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package playlist.server.exception; | ||
|
||
public class DataFetchException extends BaseException { | ||
|
||
public static final BaseException EXCEPTION = new DataFetchException(); | ||
|
||
public DataFetchException() { | ||
super(GlobalException.DATA_FETCH_ERROR); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package playlist.server.exception; | ||
|
||
public class InvalidParameterException extends BaseException { | ||
|
||
public static final BaseException EXCEPTION = new InvalidParameterException(); | ||
|
||
public InvalidParameterException() { | ||
super(GlobalException.INVALID_PARAMETER_ERROR); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package playlist.server.exception; | ||
|
||
public class LikeIncrementException extends BaseException { | ||
|
||
public static final BaseException EXCEPTION = new LikeIncrementException(); | ||
|
||
public LikeIncrementException() { | ||
super(GlobalException.LIKE_INCREMENT_ERROR); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package playlist.server.exception; | ||
|
||
import playlist.server.exception.BaseErrorCode; | ||
import playlist.server.exception.BaseException; | ||
import static org.springframework.http.HttpStatus.NOT_FOUND; | ||
|
||
public class TagNotFoundException extends BaseException { | ||
|
||
public static final BaseException EXCEPTION = new TagNotFoundException(); | ||
|
||
private TagNotFoundException() { | ||
super(GlobalException.TAG_NOT_FOUND); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package playlist.server.exception; | ||
|
||
public class ViewIncrementException extends BaseException { | ||
|
||
public static final BaseException EXCEPTION = new ViewIncrementException(); | ||
|
||
public ViewIncrementException() { | ||
super(GlobalException.VIEW_INCREMENT_ERROR); | ||
} | ||
} |
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 RankingInfo { | ||
DAILY("daily_like", "daily_view"), | ||
WEEK("week_like", "week_view"), | ||
MONTH("month_like", "month_view"); | ||
|
||
private final String likeKey; | ||
private final String viewKey; | ||
} |
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.
태그로 어울리는 정보를 전달하는거 매우 좋아보입니다 ^^