-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat] 가장 많이 검색된 책 조회 api 개발 및 책 상세 검색 시 검색카운트 업데이트 로직 추가 #49
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
Changes from all commits
7f5ec67
15870ee
e3971c2
7eb5073
0117897
ebd6d8f
8751a96
51db2a4
ecec33c
5df05e4
abd294c
c3b276b
da1a08d
19184a7
6f78577
5a23a82
0f663b5
31266bd
ab2e398
d8377b8
47d1201
003d72c
96a5840
cedaba8
3c56997
a826f7d
691bcbb
9acf60f
671ca86
80eb90e
de5fd7d
5cd9e04
5e53557
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 |
|---|---|---|
|
|
@@ -35,21 +35,26 @@ jobs: | |
| settings-path: ${{ github.workspace }} # location for the settings.xml file | ||
|
|
||
|
|
||
| - name: 🧾 Create application.yml from secret | ||
| # - name: 🧾 Create application.yml from secret | ||
| # run: | | ||
| # mkdir -p ${{ env.RESOURCE_PATH }} | ||
| # mkdir -p ${{ env.TEST_RESOURCE_PATH }} | ||
| # echo "${{ secrets.APPLICATION_YML_DEV }}" > ${{ env.RESOURCE_PATH }}/application.yml | ||
| # echo "${{ secrets.APPLICATION_YML_TEST }}" > ${{ env.TEST_RESOURCE_PATH }}/application-test.yml | ||
| - name: 🧾 Create application.yml from secret (base64) | ||
| run: | | ||
| mkdir -p ${{ env.RESOURCE_PATH }} | ||
| mkdir -p ${{ env.TEST_RESOURCE_PATH }} | ||
| echo "${{ secrets.APPLICATION_YML_DEV }}" > ${{ env.RESOURCE_PATH }}/application.yml | ||
| echo "${{ secrets.APPLICATION_YML_TEST }}" > ${{ env.TEST_RESOURCE_PATH }}/application-test.yml | ||
|
|
||
| echo "${{ secrets.APPLICATION_YML_DEV }}" | base64 --decode > ${{ env.RESOURCE_PATH }}/application.yml | ||
| echo "${{ secrets.APPLICATION_YML_TEST }}" | base64 --decode > ${{ env.TEST_RESOURCE_PATH }}/application-test.yml | ||
|
|
||
| - name: 👏🏻 grant execute permission for gradlew | ||
| run: chmod +x gradlew | ||
|
|
||
| # - name: 🚀 Start Redis | ||
| # uses: supercharge/redis-github-action@1.7.0 | ||
| # with: | ||
| # redis-version: 7 | ||
| - name: 🚀 Start Redis | ||
| uses: supercharge/redis-github-action@1.7.0 | ||
| with: | ||
| redis-version: 7 | ||
|
Comment on lines
+54
to
+57
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 액션 버전 태그 고정으로 공급망 위험 완화 🤖 Prompt for AI Agents |
||
|
|
||
| - name: 🐘 build with Gradle | ||
| run: ./gradlew build | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package konkuk.thip.book.adapter.in.web.response; | ||
|
|
||
| import konkuk.thip.book.application.port.in.dto.BookMostSearchResult; | ||
| import lombok.Builder; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record GetBookMostSearchResponse( | ||
| List<BookMostSearchResult.BookRankInfo> bookList | ||
| ) { | ||
| public static GetBookMostSearchResponse of(BookMostSearchResult bookMostSearchResult) { | ||
| return new GetBookMostSearchResponse(bookMostSearchResult.bookList()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,10 @@ | |
| import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| public interface BookJpaRepository extends JpaRepository<BookJpaEntity, Long> { | ||
| Optional<BookJpaEntity> findByIsbn(String isbn); | ||
| List<BookJpaEntity> findByIsbnIn(List<String> isbnList); | ||
|
Contributor
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 konkuk.thip.book.adapter.out.persistence; | ||
|
|
||
| import konkuk.thip.book.adapter.out.mapper.BookMapper; | ||
| import konkuk.thip.book.application.port.out.BookQueryPort; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class BookQueryPersistenceAdapter implements BookQueryPort { | ||
|
|
||
| private final BookJpaRepository bookJpaRepository; | ||
| private final BookMapper bookMapper; | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,135 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| package konkuk.thip.book.adapter.out.persistence; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.core.type.TypeReference; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.book.application.port.in.dto.BookMostSearchResult; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.book.application.port.out.BookRedisCommandPort; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.book.application.port.out.BookRedisQueryPort; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.exception.ExternalApiException; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.exception.code.ErrorCode; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.data.redis.core.RedisTemplate; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.data.redis.core.ZSetOperations; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.Duration; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.format.DateTimeFormatter; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Collections; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Set; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import static konkuk.thip.common.exception.code.ErrorCode.JSON_PROCESSING_ERROR; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||
| public class BookRedisAdapter implements BookRedisQueryPort, BookRedisCommandPort { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private final RedisTemplate<String, String> redisTemplate; | ||||||||||||||||||||||||||||||||||||||||||||||||
| private final DateTimeFormatter DAILY_KEY_FORMATTER = | ||||||||||||||||||||||||||||||||||||||||||||||||
| DateTimeFormatter.ofPattern("yyyyMMdd"); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${app.redis.search-count-prefix}") | ||||||||||||||||||||||||||||||||||||||||||||||||
| private String searchCountPrefix; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${app.redis.search-rank-prefix}") | ||||||||||||||||||||||||||||||||||||||||||||||||
| private String searchRankPrefix; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${app.redis.search-rank-detail-prefix}") | ||||||||||||||||||||||||||||||||||||||||||||||||
| private String searchRankDetailPrefix; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private final ObjectMapper objectMapper; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public List<Map.Entry<String, Double>> getBookSearchRank(LocalDate date, int topN) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return getTopNFromZSet(searchRankPrefix, date, topN); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public List<Map.Entry<String, Double>> getBookSearchCountTopN(LocalDate date, int topN) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return getTopNFromZSet(searchCountPrefix, date, topN); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private List<Map.Entry<String, Double>> getTopNFromZSet(String prefix, LocalDate date, int topN) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| String dateStr = date.format(DAILY_KEY_FORMATTER); | ||||||||||||||||||||||||||||||||||||||||||||||||
| String redisKey = prefix + dateStr; | ||||||||||||||||||||||||||||||||||||||||||||||||
| Set<ZSetOperations.TypedTuple<String>> topNSet = redisTemplate.opsForZSet() | ||||||||||||||||||||||||||||||||||||||||||||||||
| .reverseRangeWithScores(redisKey, 0, topN - 1); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (topNSet == null) return Collections.emptyList(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return topNSet.stream() | ||||||||||||||||||||||||||||||||||||||||||||||||
| .map(tuple -> Map.entry(tuple.getValue(), tuple.getScore())) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .collect(Collectors.toList()); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public List<BookMostSearchResult.BookRankInfo> getYesterdayBookRankInfos(LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| String redisKey = searchRankDetailPrefix + date.format(DAILY_KEY_FORMATTER); | ||||||||||||||||||||||||||||||||||||||||||||||||
| String json = redisTemplate.opsForValue().get(redisKey); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (json == null || json.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return List.of(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return objectMapper.readValue( | ||||||||||||||||||||||||||||||||||||||||||||||||
| json, | ||||||||||||||||||||||||||||||||||||||||||||||||
| new TypeReference<List<BookMostSearchResult.BookRankInfo>>() {} | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (JsonProcessingException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ExternalApiException(ErrorCode.JSON_PROCESSING_ERROR); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public void incrementBookSearchCount(String isbn, LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| String redisKey = makeRedisKey(searchCountPrefix, date); | ||||||||||||||||||||||||||||||||||||||||||||||||
| redisTemplate.opsForZSet().incrementScore(redisKey, isbn, 1.0); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+87
to
+91
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. 🛠️ Refactor suggestion 검색 카운트 키에도 TTL을 설정하여 데이터 누적을 방지하세요. 스케줄러가 이전 날짜의 카운트를 삭제하지만, 스케줄러 실패 시 키가 무한정 누적될 수 있습니다. 안전성을 위해 TTL을 설정하는 것을 권장합니다. @Override
public void incrementBookSearchCount(String isbn, LocalDate date) {
String redisKey = makeRedisKey(searchCountPrefix, date);
redisTemplate.opsForZSet().incrementScore(redisKey, isbn, 1.0);
+ // 스케줄러 실패에 대비한 안전장치로 8일 TTL 설정
+ redisTemplate.expire(redisKey, Duration.ofDays(8));
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public void saveBookSearchRank(List<String> isbns, List<Double> scores, LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| String redisKey = makeRedisKey(searchRankPrefix, date); | ||||||||||||||||||||||||||||||||||||||||||||||||
| for (int i = 0; i < isbns.size(); i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| redisTemplate.opsForZSet().add(redisKey, isbns.get(i), scores.get(i)); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| redisTemplate.expire(redisKey, Duration.ofDays(7)); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public void saveBookSearchRankDetail(List<BookMostSearchResult.BookRankInfo> bookRankDetails, LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| String redisKey = makeRedisKey(searchRankDetailPrefix, date); | ||||||||||||||||||||||||||||||||||||||||||||||||
| String detailJson; | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| detailJson = objectMapper.writeValueAsString(bookRankDetails); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (JsonProcessingException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ExternalApiException(JSON_PROCESSING_ERROR); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| redisTemplate.opsForValue().set(redisKey, detailJson); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+102
to
+112
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. 상세 순위 데이터에도 TTL을 설정해야 합니다. PR 목표에 따르면 순위 데이터는 7일 TTL을 가져야 하는데, 상세 데이터에는 TTL이 설정되지 않아 데이터가 영구적으로 남을 수 있습니다. @Override
public void saveBookSearchRankDetail(List<BookMostSearchResult.BookRankInfo> bookRankDetails, LocalDate date) {
String redisKey = makeRedisKey(searchRankDetailPrefix, date);
String detailJson;
try {
detailJson = objectMapper.writeValueAsString(bookRankDetails);
} catch (JsonProcessingException e) {
throw new ExternalApiException(JSON_PROCESSING_ERROR);
}
redisTemplate.opsForValue().set(redisKey, detailJson);
+ redisTemplate.expire(redisKey, Duration.ofDays(7));
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public void deleteBookSearchRank(LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| deleteZSetKey(searchRankPrefix, date); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public void deleteBookSearchCount(LocalDate date) { deleteZSetKey(searchCountPrefix, date); } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public void deleteBookSearchRankDetail(LocalDate date) { deleteZSetKey(searchRankDetailPrefix, date); } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private void deleteZSetKey(String prefix, LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| String redisKey = makeRedisKey(prefix, date); | ||||||||||||||||||||||||||||||||||||||||||||||||
| redisTemplate.delete(redisKey); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private String makeRedisKey(String prefix, LocalDate date) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| String dateStr = date.format(DAILY_KEY_FORMATTER); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return prefix + dateStr; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package konkuk.thip.book.application.port.in; | ||
|
|
||
| import konkuk.thip.book.application.port.in.dto.BookMostSearchResult; | ||
|
|
||
| public interface BookMostSearchUseCase { | ||
| BookMostSearchResult getMostSearchedBooks(Long userId); | ||
|
Collaborator
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. p2 : userId 메서드는 사용되지 않고 있습니다! |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package konkuk.thip.book.application.port.in.dto; | ||
|
|
||
| import konkuk.thip.book.adapter.in.web.response.GetBookMostSearchResponse; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| import lombok.Builder; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record BookMostSearchResult( | ||
| List<BookRankInfo> bookList | ||
| ) { | ||
| @Builder | ||
| public record BookRankInfo( | ||
| int rank, | ||
| String title, | ||
| String imageUrl, | ||
| String isbn | ||
| ) {} | ||
|
Comment on lines
+11
to
+17
Collaborator
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. 엇 전 Result, response 각각의 inner class 를 얘기한 거긴 한데, 완전히 중복되는 코드라 의미없다고 생각하신 거면 지금 상태도 괜찮습니다!! 조회로직시에 result, response 를 어디까지 분리할지를 한번 얘기나눠봐도 좋을 것 같습니다
Member
Author
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 BookMostSearchResult of(List<BookRankInfo> bookList) { | ||
| return new BookMostSearchResult(bookList); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,4 @@ | ||
| package konkuk.thip.book.application.port.out; | ||
|
|
||
| public interface BookQueryPort { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package konkuk.thip.book.application.port.out; | ||
|
|
||
| import konkuk.thip.book.application.port.in.dto.BookMostSearchResult; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| public interface BookRedisCommandPort { | ||
| void incrementBookSearchCount(String isbn, LocalDate date); | ||
| void saveBookSearchRank(List<String> isbns, List<Double> scores, LocalDate date); | ||
| void saveBookSearchRankDetail(List<BookMostSearchResult.BookRankInfo> bookRankDetails, LocalDate date); | ||
| void deleteBookSearchRank(LocalDate date); | ||
| void deleteBookSearchCount(LocalDate date); | ||
| void deleteBookSearchRankDetail(LocalDate date); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package konkuk.thip.book.application.port.out; | ||
|
|
||
| import konkuk.thip.book.application.port.in.dto.BookMostSearchResult; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public interface BookRedisQueryPort { | ||
| List<Map.Entry<String, Double>> getBookSearchRank(LocalDate date, int topN); | ||
| List<Map.Entry<String, Double>> getBookSearchCountTopN(LocalDate date, int topN); | ||
| List<BookMostSearchResult.BookRankInfo> getYesterdayBookRankInfos(LocalDate date); | ||
| } |
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.
🛠️ Refactor suggestion
echo→printf로 치환해 다중 라인 secret 디코딩 신뢰성 향상echo는 개행·백슬래시 해석 등으로 secret 내용이 변형될 가능성이 있습니다.printf '%s'를 사용하면 내용을 그대로 전달할 수 있어 안전합니다.추가로, 디코딩 실패 시 조기에 잡을 수 있도록
set -euo pipefail선언을 고려해 보세요.📝 Committable suggestion
🤖 Prompt for AI Agents