Skip to content

Commit

Permalink
[FEATURE] 인기곡 랭킹 기능 구현 중간점검 (#27)
Browse files Browse the repository at this point in the history
* Save current work

* Save current work

* Save current work

* 랭킹 접속세기 및 노래방기기별 번호조회 구현

* chore : 충돌해결

* rankController 수정

* 형변환 수정

* [#15] refactor : Repository 의 Optional Collection 형태를 수정

---------

Co-authored-by: Pingpong <[email protected]>
Co-authored-by: Jong1 <[email protected]>
Co-authored-by: jake <[email protected]>
  • Loading branch information
4 people authored Jun 28, 2024
1 parent 368fe46 commit dd68bb7
Show file tree
Hide file tree
Showing 22 changed files with 473 additions and 3 deletions.
4 changes: 4 additions & 0 deletions core/core-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ dependencies {
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

// redis
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '3.3.1'
}

bootJar.enabled = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ public static void main(String[] args) {
SpringApplication.run(CoreApiApplication.class, args);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.nerd.favorite18.core.api._common.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* Redis 환경 설정
*/
@Configuration
public class RedisConfig {

@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private int port;

/**
* Redis 와의 연결을 위한 'Connection' 생성.
*/
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}

/**
* Redis 데이터 처리를 위한 템플릿 구성.
* 해당 구성된 RedisTemplate을 통해서 데이터 통신으로 처리되는 대한 직렬화 수행.
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// Redis 연결.
redisTemplate.setConnectionFactory(redisConnectionFactory());

// Key-Value 형태로 직렬화 수행.
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());

redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.nerd.favorite18.core.api.ranking.business;

import com.nerd.favorite18.core.api._common.annotation.Business;
import com.nerd.favorite18.core.api.ranking.dto.RankDto;
import com.nerd.favorite18.core.api.ranking.service.RankService;
import com.nerd.favorite18.core.enums.song.MachineType;
import com.nerd.favorite18.storage.db.core.ranking.entity.Rank;
import com.nerd.favorite18.storage.db.core.ranking.projection.RankListResponse;
import lombok.RequiredArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@RequiredArgsConstructor
@Business
public class RankBusiness {
private final RankService rankService;

public List<RankListResponse> getRankAll(LocalDate rankDate, MachineType machineType) {

return rankService.getRankAll(rankDate, machineType);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.nerd.favorite18.core.api.ranking.controller;

import com.nerd.favorite18.core.api._common.support.response.ApiResponse;
import com.nerd.favorite18.core.api.ranking.business.RankBusiness;
import com.nerd.favorite18.core.api.ranking.dto.RankDto;
import com.nerd.favorite18.core.enums.song.MachineType;
import com.nerd.favorite18.storage.db.core.ranking.projection.RankListResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/v1/songs/rank")
@RestController
public class RankController {
private final RankBusiness rankBusiness;

@GetMapping
public ApiResponse<List<RankListResponse>> getRankAll(@RequestParam LocalDate today, @RequestParam MachineType machineType) {
List<RankListResponse> response = rankBusiness.getRankAll(today, machineType);

return ApiResponse.success(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.nerd.favorite18.core.api.ranking.controller;

import com.nerd.favorite18.core.api.ranking.service.RedisRankService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/v1/songs/rank")
@RestController
public class RankRadisController {

private final RedisRankService redisRankService;

@GetMapping("/redis/rank/{songId}")
public String getSongCount(@PathVariable Long songId) {
return redisRankService.getSongCounts(songId);
}

@PostMapping("/redis/rank")
public void setSongCount(@RequestBody HashMap<String, String> body) {
Long songId = Long.valueOf(body.get("songId").toString());
String searchCnt = body.get("searchCount").toString();
redisRankService.setSongCounts(songId, searchCnt);
}

@PostMapping("/redis/rank/increment/{songId}")
public ResponseEntity<String> incrementSongCount(@PathVariable Long songId) {
// 여기서는 간단히 조회수를 1 증가시킨다고 가정
redisRankService.incrementSongCount(songId);
return ResponseEntity.ok("Song count incremented for songId: " + songId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.nerd.favorite18.core.api.ranking.converter;

import com.nerd.favorite18.core.api._common.annotation.Converter;
import com.nerd.favorite18.core.api.ranking.dto.RankDto;
import com.nerd.favorite18.storage.db.core.ranking.entity.Rank;

import java.util.List;
import java.util.stream.Collectors;

@Converter
public class RankConverter {
public RankDto toDto(Rank entity) {
return RankDto.of(
entity.getId(),
entity.getRankSong(),
entity.getRankDate(),
entity.getRank(),
entity.getSearchCnt(),
entity.getCreatedAt(),
entity.getUpdatedAt()
);
}
public List<RankDto> toDto(List<Rank> entities) {
return entities.stream()
.map(this::toDto)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.nerd.favorite18.core.api.ranking.dto;

import com.nerd.favorite18.storage.db.core.song.entity.Song;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.sql.Date;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RankDto {
private Long id;
private Song rankSong;
private LocalDate rankDate;
private String rank;
private Long searchCnt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;

public static RankDto of(
Long id,
Song rankSong,
LocalDate rankDate,
String rank,
long searchCnt,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
return new RankDto(
id,
rankSong,
rankDate,
rank,
searchCnt,
createdAt,
updatedAt
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.nerd.favorite18.core.api.ranking.dto.request;

import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class RankAddRequest {
private String title;
private String artist;
private Long searchCnt;
private String machineType;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.nerd.favorite18.core.api.ranking.service;

import com.nerd.favorite18.core.api.ranking.converter.RankConverter;
import com.nerd.favorite18.core.enums.song.MachineType;
import com.nerd.favorite18.storage.db.core.ranking.projection.RankListResponse;
import com.nerd.favorite18.storage.db.core.ranking.repository.RankRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Service
public class RankService {

private final RankRepository rankRepository;
private final RankConverter rankConverter;
private final RedisTemplate<String, Object> redisTemplate;

@Transactional(readOnly = true)
public List<RankListResponse> getRankAll(LocalDate rankDate, MachineType machineType) {
final List<RankListResponse> ranks = rankRepository.findByRankDateAndMachineTyperOrderBySearchCntDesc(rankDate, machineType);

return ranks;
}

public void increaseSearchCnt(Long songId) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
redisTemplate.opsForZSet().incrementScore("searchCnt",songId, 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.nerd.favorite18.core.api.ranking.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Slf4j
@RequiredArgsConstructor
@Service
public class RedisRankService {

private final RedisTemplate redisTemplate;
private static final String REDIS_KEY_PREFIX = "rank:songs:";

public String getSongCounts(Long songId){
ValueOperations<String, String> values = redisTemplate.opsForValue();
String key = REDIS_KEY_PREFIX + songId.toString();
return values.get(key);
}

public void setSongCounts(Long songId, String searchCnt) {
ValueOperations<String, String> values = redisTemplate.opsForValue();
String key = REDIS_KEY_PREFIX + songId.toString();
values.set(key, searchCnt, Duration.ofDays(1)); // 1일 뒤 메모리에서 삭제.
}

public void incrementSongCount(Long songId) {
String key = REDIS_KEY_PREFIX + songId.toString();
redisTemplate.opsForValue().increment(key);
}
}
4 changes: 4 additions & 0 deletions core/core-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ spring:
- db-core.yml
mvc.throw-exception-if-no-handler-found: true
# web.resources.add-mappings: false
data:
redis:
host: localhost
port: 6379

server:
tomcat:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.nerd.favorite18;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class CoreApiDbTestApplication {

@Test
void contextLoad() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.nerd.favorite18.service;

import com.nerd.favorite18.core.api.ranking.service.RankService;
import com.nerd.favorite18.core.enums.song.MachineType;
import com.nerd.favorite18.storage.db.core.ranking.entity.Rank;
import com.nerd.favorite18.storage.db.core.ranking.repository.RankRepository;
import com.nerd.favorite18.storage.db.core.song.entity.Song;
import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.LocalDate;
import java.time.LocalDateTime;

@ExtendWith(SpringExtension.class) // junit4의 RunWith
@SpringBootTest
//@Transactional
public class RankServiceTest {

@Autowired
EntityManager em;
@Autowired
RankRepository rankRepository;
@Autowired
RankService rankService;
@Test
public void 랭킹조회() throws Exception {
//given
//Rank rank = createRank();
LocalDate today = LocalDate.now(Clock.systemDefaultZone());
//when
rankService.getRankAll(today, MachineType.TJ);
//then

}
}
1 change: 0 additions & 1 deletion core/core-batch/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ dependencies {
implementation project(":storage:db-core")

implementation 'org.springframework.boot:spring-boot-starter-batch'

}
Loading

0 comments on commit dd68bb7

Please sign in to comment.