Skip to content

Conversation

@KiSeungMin
Copy link
Member

@KiSeungMin KiSeungMin commented Jul 17, 2025

✔️ 연관 이슈

📝 작업 내용

  • 유저가 웨이블존 상세 조회 시 방문 로그 객체 남기기 구현 완료
  • 거리, 유사 사용자 집단, 최근 추천 일 수를 기반으로 한 추천 시스템 구현 완료
    • 거리가 가까울 수록 거리 점수가 높아집니다. (거리 가중치: 55%)
    • 특정 가게에 대해 같은 연령대 or 같은 성별을 가진 유저가 조회를 자주 할 수록 점수가 높아집니다.(유사 사용자 가중치: 15%)
      • 같은 연령대 + 성별 -> 1.0
      • 같은 연령대 + 다른 성별 -> 0.7
      • 다른 연령대 + 같은 성별 -> 0.2
    • 최근에 추천한지 얼마 안 되었다면 감점을 적용합니다. (가중치: 30%)
      • 하루 전에 추천했을 시 감점 최대
      • 마지막 추천일시가 30일이 넘었다면 감점 X
      • 30일에서 1일까지 선형적으로 증가하는 감점 적용

스크린샷 (선택)

Summary by CodeRabbit

  • 신규 기능

    • 웨이블존 개인 맞춤 추천 API가 도입되어, 사용자의 위치와 선호도에 기반한 맞춤형 웨이블존 추천 목록을 제공합니다.
    • 방문 로그 및 추천 로그 관리 기능이 추가되어, 사용자의 방문 및 추천 이력을 추적할 수 있습니다.
    • 방문 로그 저장 서비스가 추가되어, 사용자의 웨이블존 방문 기록을 관리합니다.
    • 추천 로그 저장 및 갱신 기능이 도입되어, 일별 추천 이력을 기록하고 관리합니다.
    • 추천 오류 케이스 및 방문 로그 오류 케이스가 추가되어, 상세한 예외 처리가 가능합니다.
  • 기능 개선

    • 추천 응답이 단일 객체에서 리스트 형태로 확장되어 여러 추천 결과를 한 번에 받을 수 있습니다.
    • 추천 결과에 거리, 유사도, 최신성 점수 등 다양한 평가 지표와 상세 정보가 포함됩니다.
    • 추천 로직이 사용자 연령대 및 성별 기반 유사도, 거리, 최근성 점수를 반영하는 복합 알고리즘으로 개선되었습니다.
    • 추천 API가 요청 파라미터로 위치와 결과 수를 받아 유연하게 동작하도록 변경되었습니다.
    • 추천 컨트롤러가 입력 DTO를 활용해 유효성 검증 및 다양한 조건을 지원합니다.
    • 방문 로그 및 추천 로그의 ID 타입이 Long에서 String으로 변경되어 데이터 일관성이 향상되었습니다.
  • 테스트

    • 웨이블존 추천 API의 통합 테스트가 추가되어, 추천 기능의 신뢰성과 정확성을 검증합니다.
    • 대량의 샘플 데이터 생성 및 추천 결과 검증 테스트가 포함되었습니다.

@KiSeungMin KiSeungMin self-assigned this Jul 17, 2025
@KiSeungMin KiSeungMin added the 💡 feature 기능 구현 및 개발 label Jul 17, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 17, 2025

"""

Walkthrough

이 변경사항은 Wayble Zone의 개인화 추천 시스템을 구현합니다. 추천 컨트롤러, DTO, 서비스, 저장소, 엔티티가 대폭 확장되어 거리, 유사 사용자 관심도, 최근 추천 이력 등 다양한 가중치 요소를 반영한 추천 알고리즘이 도입되었습니다. 통합 테스트도 추가되었습니다.

Changes

파일/경로 요약 변경 요약
.../config/ElasticsearchConfig.java EnableElasticsearchRepositories import 제거
.../controller/WaybleZoneRecommendController.java 추천 API 엔드포인트를 단일 DTO 반환에서 조건 DTO 및 size 파라미터 기반 리스트 반환으로 변경
.../dto/recommend/WaybleZoneRecommendConditionDto.java 위치와 사용자 ID를 포함하는 조건 DTO 신설, 유효성 검증 추가
.../dto/recommend/WaybleZoneRecommendResponseDto.java 추천 응답 DTO를 다수 필드 및 builder, factory method로 확장
.../entity/RecommendLogDocument.java 추천 로그 Elasticsearch 도큐먼트 엔티티 신설
.../entity/WaybleZoneVisitLogDocument.java 방문 로그의 logId 타입 Long → String 변경, UUID 기반 생성 로직 수정
.../exception/RecommendErrorCase.java 추천 로그 및 웨이블존 미존재 에러케이스 추가
.../exception/VisitLogErrorCase.java 방문 로그 관련 에러케이스 enum 신설
.../repository/RecommendLogDocumentRepository.java 추천 로그 도큐먼트용 Elasticsearch 저장소 신설 및 쿼리 메서드 추가
.../repository/WaybleZoneVisitLogDocumentRepository.java 방문 로그 저장소의 ID 타입 Long→String, findAll() 메서드 추가
.../repository/recommend/WaybleZoneQueryRecommendRepository.java 개인화 추천 알고리즘 구현: 거리, 유사 사용자, 최근 추천 가중치 반영, 복합 스코어 계산
.../repository/search/WaybleZoneQuerySearchRepository.java 주석 영어→한글 변환(기능 변화 없음)
.../service/WaybleZoneRecommendService.java 추천 서비스가 리스트 반환 및 추천 로그 저장/갱신 로직, 오늘의 추천 조회 등 추가
.../service/WaybleZoneVisitLogService.java 방문 로그 저장 서비스 신설, 유저 검증 및 로그 저장 로직 구현
.../wayblezone/controller/WaybleZoneController.java 방문 로그 서비스 필드 추가 및 추후 방문 로그 기록용 주석 추가(구현X)
.../WaybleZoneRecommendApiIntegrationTest.java 대규모 샘플 데이터 생성, 추천 API 통합 테스트 신설, 응답·로그 검증 포함
.../WaybleZoneSearchApiIntegrationTest.java 패키지명만 변경(기능 변화 없음)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant WaybleZoneRecommendController
    participant WaybleZoneRecommendService
    participant WaybleZoneQueryRecommendRepository
    participant RecommendLogDocumentRepository
    participant WaybleZoneDocumentRepository

    Client->>WaybleZoneRecommendController: GET /recommend?userId&lat&lon&size
    WaybleZoneRecommendController->>WaybleZoneRecommendService: getWaybleZonePersonalRecommend(userId, lat, lon, size)
    alt size == 1 and 오늘의 추천 존재
        WaybleZoneRecommendService->>RecommendLogDocumentRepository: findByUserIdAndRecommendationDate(userId, today)
        WaybleZoneRecommendService->>WaybleZoneDocumentRepository: findById(zoneId)
        WaybleZoneRecommendService-->>WaybleZoneRecommendController: 오늘의 추천 반환
    else
        WaybleZoneRecommendService->>WaybleZoneQueryRecommendRepository: searchPersonalWaybleZones(user, lat, lon, size)
        WaybleZoneQueryRecommendRepository->>...: 거리, 유사도, 최근 추천 이력 기반 복합 스코어 계산
        WaybleZoneQueryRecommendRepository-->>WaybleZoneRecommendService: 추천 리스트 반환
        alt size == 1
            WaybleZoneRecommendService->>RecommendLogDocumentRepository: existsByUserIdAndZoneId
            alt 존재
                WaybleZoneRecommendService->>RecommendLogDocumentRepository: updateRecommendLog
            else
                WaybleZoneRecommendService->>RecommendLogDocumentRepository: saveRecommendLog
            end
        end
        WaybleZoneRecommendService-->>WaybleZoneRecommendController: 추천 리스트 반환
    end
    WaybleZoneRecommendController-->>Client: 추천 결과 반환
Loading

Assessment against linked issues

Objective Addressed Explanation
가중치 기반 추천 시스템 구현 (#29)
거리, 유사 사용자(성별/연령), 최근 추천일 가중치 반영 (#29)

Possibly related PRs

Suggested reviewers

  • seung-in-Yoo
  • hyoinYang

Poem

🐰
토끼 귀를 쫑긋 세우고
추천 알고리즘을 짜네
거리, 유사도, 시간까지
모두 담아 쿵짝쿵짝
사용자 마음에 쏙 드는
웨이블존 추천,
오늘도 토끼는 행복해!
🌟
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (9)
src/main/java/com/wayble/server/wayblezone/controller/WaybleZoneController.java (1)

59-61: 방문 로그 기능 구현 완료 필요

JWT 토큰에서 사용자 ID를 추출하여 방문 로그를 기록하는 기능이 아직 구현되지 않았습니다. 추천 시스템의 핵심 기능 중 하나이므로 우선순위를 높여 구현해야 합니다.

JWT 토큰 파싱 및 방문 로그 저장 로직 구현을 도와드릴까요?

src/main/java/com/wayble/server/explore/service/WaybleZoneVisitLogService.java (1)

15-15: 클래스 레벨의 @Transactional 사용 재고려

클래스 레벨에 @Transactional을 적용하면 모든 public 메서드가 트랜잭션 내에서 실행됩니다. 읽기 전용 메서드가 추가될 경우 불필요한 트랜잭션 오버헤드가 발생할 수 있습니다.

메서드 레벨로 이동하여 필요한 메서드에만 적용하는 것을 고려하세요:

-@Transactional
 public class WaybleZoneVisitLogService {
     
+    @Transactional
     public void saveVisitLog(Long userId, Long zoneId) {
src/main/java/com/wayble/server/explore/repository/recommend/WaybleZoneQueryRecommendRepository.java (1)

87-93: 유사도 가중치 계산 로직 문서화 필요

연령대와 성별 일치에 따른 가중치(1.0, 0.7, 0.2)가 임의로 설정된 것처럼 보입니다. 이러한 값들의 근거와 계산 방식을 문서화해주세요.

// 유사도 가중치 계산 로직
// - 연령대와 성별 모두 일치: 1.0 (100% 유사도)
// - 연령대만 일치: 0.7 (70% 유사도) - 연령대가 더 중요한 요소로 판단
// - 성별만 일치: 0.2 (20% 유사도) - 성별은 보조적 요소
if (ageMatch && genderMatch) {
    weight = 1.0;
} else if (ageMatch) {
    weight = 0.7;
} else if (genderMatch) {
    weight = 0.2;
}
src/test/java/com/wayble/server/explore/WaybleZoneRecommendApiIntegrationTest.java (6)

79-91: 불변 리스트를 static final로 선언하세요

nameList는 변경되지 않으므로 static final로 선언하여 메모리 효율성을 높이고 의도를 명확히 하세요.

-List<String> nameList = new ArrayList<>(Arrays.asList(
+private static final List<String> NAME_LIST = List.of(
        "던킨도너츠",
        "베스킨라빈스",
        "투썸플레이스",
        "스타벅스",
        "메가엠지씨커피",
        "공차",
        "롯데리아",
        "맥도날드",
        "KFC",
        "노브랜드버거"
-));
+);

125-125: 변경된 상수명을 사용하세요

이전 제안사항을 적용한다면 여기서도 변경이 필요합니다.

-.zoneName(nameList.get((int) (Math.random() * nameList.size())))
+.zoneName(NAME_LIST.get((int) (Math.random() * NAME_LIST.size())))

94-161: 테스트 데이터 생성 성능을 개선하세요

현재 구현은 각 엔티티를 개별적으로 저장하고 있어 성능이 저하될 수 있습니다. 벌크 저장을 사용하면 테스트 실행 시간을 크게 단축할 수 있습니다.

// 추천 로그 벌크 저장
List<RecommendLogDocument> recommendLogs = new ArrayList<>();
Set<String> processedPairs = new HashSet<>();
for (int i = 1; i <= SAMPLES / 2; i++) {
    Long zoneId = (long) (Math.random() * SAMPLES) + 1;
    String key = userId + "-" + zoneId;
    if (!processedPairs.contains(key)) {
        processedPairs.add(key);
        recommendLogs.add(RecommendLogDocument.builder()
                .id(UUID.randomUUID().toString())
                .userId(userId)
                .zoneId(zoneId)
                .recommendationDate(makeRandomDate())
                .recommendCount(1L)
                .build());
    }
}
recommendLogDocumentRepository.saveAll(recommendLogs);

// WaybleZone과 User도 유사하게 처리
List<WaybleZoneDocument> waybleZones = new ArrayList<>();
List<User> users = new ArrayList<>();
List<WaybleZoneVisitLogDocument> visitLogs = new ArrayList<>();
// ... 데이터 생성 로직
waybleZoneDocumentRepository.saveAll(waybleZones);
userRepository.saveAll(users);
waybleZoneVisitLogDocumentRepository.saveAll(visitLogs);

176-184: System.out.println 대신 로거를 사용하세요

테스트에서도 적절한 로깅을 사용하는 것이 좋습니다.

@Slf4j
public class WaybleZoneRecommendApiIntegrationTest {
    // ...
    
    // System.out.println 대신:
    log.info("=== 웨이블존 목록 ===");
    log.info("존 정보: {}", doc.toString());
    // 등등...
}

Also applies to: 189-193, 227-233, 265-278, 314-327


252-252: 변수명 규칙을 준수하세요

Java 명명 규칙에 따라 변수명은 소문자로 시작해야 합니다.

-List<WaybleZoneRecommendResponseDto> WaybleZoneRecommendResponseDtoList = objectMapper.convertValue(
+List<WaybleZoneRecommendResponseDto> waybleZoneRecommendResponseDtoList = objectMapper.convertValue(

341-345: 랜덤 날짜 생성 범위를 명확히 문서화하세요

현재 구현은 1-40일 전의 날짜를 생성하지만, PR 목표에서는 30일 이상이면 패널티가 0이라고 했습니다. 테스트 목적을 명확히 하기 위해 주석을 추가하세요.

 private LocalDate makeRandomDate() {
     Random random = new Random();
+    // 1-40일 전 날짜 생성 (30일 초과 시 recency 패널티 0 확인용)
     int daysAgo = random.nextInt(40) + 1;
     return LocalDate.now().minusDays(daysAgo);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1155c79 and c8327dc.

📒 Files selected for processing (17)
  • src/main/java/com/wayble/server/common/config/ElasticsearchConfig.java (0 hunks)
  • src/main/java/com/wayble/server/explore/controller/WaybleZoneRecommendController.java (2 hunks)
  • src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendConditionDto.java (1 hunks)
  • src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendResponseDto.java (1 hunks)
  • src/main/java/com/wayble/server/explore/entity/RecommendLogDocument.java (1 hunks)
  • src/main/java/com/wayble/server/explore/entity/WaybleZoneVisitLogDocument.java (2 hunks)
  • src/main/java/com/wayble/server/explore/exception/RecommendErrorCase.java (1 hunks)
  • src/main/java/com/wayble/server/explore/exception/VisitLogErrorCase.java (1 hunks)
  • src/main/java/com/wayble/server/explore/repository/RecommendLogDocumentRepository.java (1 hunks)
  • src/main/java/com/wayble/server/explore/repository/WaybleZoneVisitLogDocumentRepository.java (1 hunks)
  • src/main/java/com/wayble/server/explore/repository/recommend/WaybleZoneQueryRecommendRepository.java (1 hunks)
  • src/main/java/com/wayble/server/explore/repository/search/WaybleZoneQuerySearchRepository.java (6 hunks)
  • src/main/java/com/wayble/server/explore/service/WaybleZoneRecommendService.java (2 hunks)
  • src/main/java/com/wayble/server/explore/service/WaybleZoneVisitLogService.java (1 hunks)
  • src/main/java/com/wayble/server/wayblezone/controller/WaybleZoneController.java (3 hunks)
  • src/test/java/com/wayble/server/explore/WaybleZoneRecommendApiIntegrationTest.java (1 hunks)
  • src/test/java/com/wayble/server/explore/WaybleZoneSearchApiIntegrationTest.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/wayble/server/common/config/ElasticsearchConfig.java
🧰 Additional context used
🧠 Learnings (1)
src/test/java/com/wayble/server/explore/WaybleZoneSearchApiIntegrationTest.java (1)
Learnt from: seung-in-Yoo
PR: Wayble-Project/wayble-spring#37
File: src/main/java/com/wayble/server/user/entity/User.java:61-62
Timestamp: 2025-07-13T15:08:59.288Z
Learning: Java에서 같은 패키지 내의 클래스들은 import 구문 없이 서로를 참조할 수 있다. com.wayble.server.user.entity 패키지 내의 클래스들은 명시적인 import 없이 사용 가능하다.
🔇 Additional comments (19)
src/main/java/com/wayble/server/explore/exception/VisitLogErrorCase.java (1)

9-12: 에러 케이스 정의가 적절합니다.

방문 로그 관련 에러 케이스들이 명확하게 정의되었습니다. HTTP 상태 코드 400과 순차적인 에러 코드 7001, 7002가 적절하게 사용되었습니다.

src/test/java/com/wayble/server/explore/WaybleZoneSearchApiIntegrationTest.java (1)

1-1: 패키지 구조 변경이 적절합니다.

explore 모듈로의 패키지 재구성에 맞춰 테스트 클래스의 패키지가 올바르게 업데이트되었습니다.

src/main/java/com/wayble/server/explore/entity/WaybleZoneVisitLogDocument.java (2)

22-22: ID 타입 변경이 적절합니다.

logId 필드의 타입을 Long에서 String으로 변경한 것은 Elasticsearch 문서에서 UUID 문자열을 사용하는 표준적인 접근 방식입니다.


34-34: UUID 생성 방식 개선이 우수합니다.

UUID.randomUUID().toString()을 사용하는 것이 이전의 비트 마스킹 방식보다 더 명확하고 표준적인 접근 방식입니다.

src/main/java/com/wayble/server/explore/repository/search/WaybleZoneQuerySearchRepository.java (1)

38-121: 코멘트의 한국어 번역이 명확합니다.

Elasticsearch 쿼리 구성 과정에 대한 각 단계별 설명이 한국어로 잘 번역되어 코드 가독성이 향상되었습니다. 기능적 로직은 변경되지 않았으며 안정성이 유지됩니다.

src/main/java/com/wayble/server/explore/repository/WaybleZoneVisitLogDocumentRepository.java (1)

9-10: 저장소 인터페이스 변경이 적절합니다.

엔티티의 ID 타입 변경에 맞춰 String 타입으로 올바르게 업데이트되었고, findAll() 메소드를 명시적으로 선언하여 인터페이스를 더 명확하게 만들었습니다.

src/main/java/com/wayble/server/wayblezone/controller/WaybleZoneController.java (2)

4-4: 새로운 서비스 의존성 추가 확인됨

WaybleZoneVisitLogService 의존성이 올바르게 추가되었습니다.


27-27: 의존성 주입이 올바르게 구현됨

WaybleZoneVisitLogService가 final 필드로 정의되어 불변성이 보장되고 있습니다.

src/main/java/com/wayble/server/explore/exception/RecommendErrorCase.java (1)

11-15: 에러 케이스 정의가 적절함

새로운 에러 케이스들이 기존 패턴을 잘 따르고 있으며, 에러 코드(6002, 6003)가 순차적으로 할당되어 있습니다. 한국어 메시지도 명확하고 이해하기 쉽습니다.

src/main/java/com/wayble/server/explore/entity/RecommendLogDocument.java (2)

16-21: Elasticsearch 문서 구조가 적절함

인덱스명과 필드 매핑이 올바르게 정의되어 있습니다. @Document 어노테이션과 @Field 어노테이션 사용이 적절합니다.


23-30: 필드 정의가 명확함

추천 로그에 필요한 핵심 필드들이 적절한 타입으로 정의되어 있습니다. 특히 recommendationDate 필드에 대한 Elasticsearch 날짜 형식 지정이 적절합니다.

src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendConditionDto.java (2)

8-23: DTO 구조와 유효성 검증이 우수함

Java record를 사용한 DTO 구조가 깔끔하고, 위도/경도에 대한 유효성 검증 범위가 올바르게 설정되어 있습니다. 빌더 패턴과의 통합도 적절합니다.


10-18: 지리적 좌표 유효성 검증이 정확함

위도 범위(-90.0 ~ 90.0)와 경도 범위(-180.0 ~ 180.0)가 국제 표준에 맞게 정의되어 있으며, 에러 메시지도 명확합니다.

src/main/java/com/wayble/server/explore/controller/WaybleZoneRecommendController.java (3)

22-34: 추천 API 리팩토링이 우수함

단일 추천에서 다중 추천으로의 변경이 적절하며, @ModelAttribute를 사용한 쿼리 파라미터 처리와 @Valid 검증이 올바르게 구현되어 있습니다. size 파라미터의 기본값 설정도 합리적입니다.


24-25: 파라미터 검증 로직 확인됨

@Valid @ModelAttribute 조합으로 DTO 검증이 올바르게 설정되어 있고, size 파라미터의 기본값이 적절합니다.


27-33: 서비스 호출 로직이 명확함

DTO에서 개별 필드를 추출하여 서비스 메서드를 호출하는 방식이 명확하고 이해하기 쉽습니다.

src/main/java/com/wayble/server/explore/repository/recommend/WaybleZoneQueryRecommendRepository.java (1)

116-116: 0으로 나누기 위험

거리가 0일 때 division by zero 에러가 발생할 수 있습니다. 사용자가 정확히 zone의 위치에 있을 때 이런 상황이 발생할 수 있습니다.

-                    double distanceScore = (1.0 / (1.0 + ((Double) hit.getSortValues().get(0) / 1000.0))) * DISTANCE_WEIGHT;
+                    Double distance = (Double) hit.getSortValues().get(0);
+                    double distanceScore = (1.0 / (1.0 + Math.max(distance / 1000.0, 0.001))) * DISTANCE_WEIGHT;

Likely an incorrect or invalid review comment.

src/test/java/com/wayble/server/explore/WaybleZoneRecommendApiIntegrationTest.java (2)

164-170: 테스트 정리 로직이 적절합니다

모든 테스트 데이터를 올바르게 정리하고 있습니다.


330-375: 유틸리티 메서드들이 잘 구현되었습니다

거리 계산, 랜덤 데이터 생성 메서드들이 모두 올바르게 구현되어 있습니다. 특히 haversine 공식과 랜덤 좌표 생성 로직이 정확합니다.

Comment on lines 32 to 35
public void updateRecommendLog(LocalDate recommendationDate, Long recommendCount) {
this.recommendationDate = recommendationDate;
this.recommendCount = recommendCount;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

업데이트 메서드에 검증 로직 추가 권장

updateRecommendLog 메서드에 입력값 검증이 없어 잘못된 데이터가 설정될 수 있습니다.

다음과 같이 검증 로직을 추가하는 것을 권장합니다:

public void updateRecommendLog(LocalDate recommendationDate, Long recommendCount) {
+    if (recommendationDate == null) {
+        throw new IllegalArgumentException("추천 날짜는 null일 수 없습니다.");
+    }
+    if (recommendCount == null || recommendCount < 0) {
+        throw new IllegalArgumentException("추천 횟수는 0 이상이어야 합니다.");
+    }
    this.recommendationDate = recommendationDate;
    this.recommendCount = recommendCount;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void updateRecommendLog(LocalDate recommendationDate, Long recommendCount) {
this.recommendationDate = recommendationDate;
this.recommendCount = recommendCount;
}
public void updateRecommendLog(LocalDate recommendationDate, Long recommendCount) {
if (recommendationDate == null) {
throw new IllegalArgumentException("추천 날짜는 null일 수 없습니다.");
}
if (recommendCount == null || recommendCount < 0) {
throw new IllegalArgumentException("추천 횟수는 0 이상이어야 합니다.");
}
this.recommendationDate = recommendationDate;
this.recommendCount = recommendCount;
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/explore/entity/RecommendLogDocument.java
around lines 32 to 35, the updateRecommendLog method lacks input validation,
which may allow invalid data to be set. Add validation checks to ensure
recommendationDate is not null and recommendCount is not negative or null before
assigning them to the fields. If validation fails, throw an appropriate
exception to prevent invalid state updates.

Comment on lines +22 to +32
public void saveVisitLog(Long userId, Long zoneId) {
if(userId == null || zoneId == null) {
return;
}

User user = userRepository.findById(userId)
.orElseThrow(() -> new ApplicationException(VisitLogErrorCase.USER_NOT_EXIST));

WaybleZoneVisitLogDocument visitLogDocument = WaybleZoneVisitLogDocument.fromEntity(user, zoneId);
visitLogDocumentRepository.save(visitLogDocument);
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Zone 유효성 검증 누락 및 중복 방문 로그 처리

현재 구현에는 두 가지 문제가 있습니다:

  1. zoneId의 유효성을 검증하지 않아 존재하지 않는 zone에 대한 방문 로그가 생성될 수 있습니다.
  2. 동일한 사용자가 같은 zone을 여러 번 방문할 때 중복 로그가 생성될 수 있습니다.

다음과 같이 개선하는 것을 권장합니다:

 public void saveVisitLog(Long userId, Long zoneId) {
     if(userId == null || zoneId == null) {
         return;
     }
 
     User user = userRepository.findById(userId)
             .orElseThrow(() -> new ApplicationException(VisitLogErrorCase.USER_NOT_EXIST));
+    
+    // Zone 존재 여부 확인
+    boolean zoneExists = waybleZoneDocumentRepository.existsById(zoneId);
+    if (!zoneExists) {
+        throw new ApplicationException(VisitLogErrorCase.ZONE_NOT_EXIST);
+    }
 
     WaybleZoneVisitLogDocument visitLogDocument = WaybleZoneVisitLogDocument.fromEntity(user, zoneId);
     visitLogDocumentRepository.save(visitLogDocument);
 }

또한 중복 방문 로그 처리 정책을 확인해주세요. 매번 새로운 로그를 생성하는 것이 의도된 동작인지요?


다음 스크립트를 실행하여 Zone 리포지토리와 WaybleZoneVisitLogDocument 클래스, 그리고 existsById 메서드 사용 여부를 확인해주세요:


🏁 Script executed:

#!/bin/bash
set -e

# Zone 관련 Repository 인터페이스 검색
rg "interface .*Zone.*Repository" -A 2

# WaybleZoneVisitLogDocument 클래스 정의 검색
rg "class WaybleZoneVisitLogDocument" -A 5

# existsById 메서드 사용 여부 검색
rg "existsById" -A 2

Length of output: 2802


🏁 Script executed:

#!/bin/bash
set -e

echo "=== WaybleZoneVisitLogService.java (lines 1-200) ==="
sed -n '1,200p' src/main/java/com/wayble/server/explore/service/WaybleZoneVisitLogService.java || true

echo -e "\n=== WaybleZoneDocumentRepository injection check ==="
rg "WaybleZoneDocumentRepository" -n src/main/java/com/wayble/server/explore/service/WaybleZoneVisitLogService.java || true

echo -e "\n=== fromEntity in WaybleZoneVisitLogDocument ==="
rg "fromEntity" -A5 src/main/java/com/wayble/server/explore/entity/WaybleZoneVisitLogDocument.java || true

Length of output: 2228


WaybleZoneVisitLogService에 Zone 존재 검증 및 중복 로그 처리 로직 추가 필요

현재 WaybleZoneVisitLogService에는 WaybleZoneDocumentRepository가 주입되어 있지 않아 zoneId의 유효성 검증이 불가능합니다. 또한 동일 사용자의 중복 방문 로그 정책이 정의되어 있지 않습니다.

수정 제안:

  • WaybleZoneDocumentRepository를 주입
  • zoneId 존재 여부 확인 (없을 경우 예외 발생)
  • 중복 로그 생성 여부(정책)에 따라 기존 로그 조회 후 저장 여부 결정

예시 diff:

 @Service
 @RequiredArgsConstructor
 @Transactional
 public class WaybleZoneVisitLogService {
 
     private final WaybleZoneVisitLogDocumentRepository visitLogDocumentRepository;
     private final UserRepository userRepository;
+    private final WaybleZoneDocumentRepository waybleZoneDocumentRepository;
 
     public void saveVisitLog(Long userId, Long zoneId) {
         if (userId == null || zoneId == null) {
             return;
         }
 
         User user = userRepository.findById(userId)
                 .orElseThrow(() -> new ApplicationException(VisitLogErrorCase.USER_NOT_EXIST));
+
+        // Zone 존재 여부 확인
+        if (!waybleZoneDocumentRepository.existsById(zoneId)) {
+            throw new ApplicationException(VisitLogErrorCase.ZONE_NOT_EXIST);
+        }
+
+        // TODO: 중복 방문 로그 처리 정책 확인
         WaybleZoneVisitLogDocument visitLogDocument = WaybleZoneVisitLogDocument.fromEntity(user, zoneId);
         visitLogDocumentRepository.save(visitLogDocument);
     }
 }
  • WaybleZoneDocumentRepository가 주입되어 있는지 확인하세요.
  • 중복 방문 시 새로운 로그를 남길지, 아니면 업데이트/무시할지 정책을 결정해 반영해 주세요.
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/explore/service/WaybleZoneVisitLogService.java
around lines 22 to 32, the code lacks validation for the existence of the zoneId
and does not handle duplicate visit logs for the same user. To fix this, inject
WaybleZoneDocumentRepository into the service, then verify if the zoneId exists
by querying this repository and throw an exception if it does not. Next,
implement logic to check for existing visit logs by the same user for the zone
and decide based on the defined policy whether to create a new log, update the
existing one, or ignore the duplicate. Adjust the saveVisitLog method
accordingly to include these validations and duplicate handling.

Comment on lines +36 to +41
// === [가중치 설정] === //
private static final double DISTANCE_WEIGHT = 0.55; // 거리 기반 점수 가중치
private static final double SIMILARITY_WEIGHT = 0.15; // 유사 사용자 방문 이력 기반 점수 가중치
private static final double RECENCY_WEIGHT = 0.3; // 최근 추천 내역 기반 감점 가중치

private static final int MAX_DAY_DIFF = 30; // 추천 감점 최대 기준일 (30일 전까지 고려)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

하드코딩된 가중치와 상수를 설정 가능하도록 변경

가중치와 상수들이 하드코딩되어 있어 비즈니스 요구사항 변경 시 코드 수정이 필요합니다. 설정 파일로 외부화하는 것을 권장합니다.

@Value("${wayble.recommendation.weight.distance:0.55}")
private double distanceWeight;

@Value("${wayble.recommendation.weight.similarity:0.15}")
private double similarityWeight;

@Value("${wayble.recommendation.weight.recency:0.3}")
private double recencyWeight;

@Value("${wayble.recommendation.max-day-diff:30}")
private int maxDayDiff;
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/explore/repository/recommend/WaybleZoneQueryRecommendRepository.java
around lines 36 to 41, the weight constants and max day difference are
hardcoded, making it difficult to update without code changes. Refactor these
static final fields to instance variables injected via @Value annotations from
configuration properties, providing default values as shown. Remove the static
modifiers and initialize these variables using Spring's @Value to externalize
configuration for easier maintenance.

Comment on lines +46 to +56
if (size == 1 && !recommendResponseDtoList.isEmpty()) {
Long zoneId = recommendResponseDtoList.get(0).zoneId();

boolean logExist = recommendLogDocumentRepository.existsByUserIdAndZoneId(userId, zoneId);
if (logExist) {
updateRecommendLog(userId, zoneId);
} else {
saveRecommendLog(userId, zoneId);
}
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

추천 로그 저장 로직의 일관성 문제

추천 로그가 size == 1일 때만 저장되고 있습니다. 이는 다음과 같은 문제를 야기할 수 있습니다:

  1. 여러 개의 추천을 요청했을 때는 추천 이력이 남지 않음
  2. 최근성 점수 계산에 영향을 줄 수 있음

모든 추천된 zone에 대해 로그를 남기거나, 사용자가 실제로 선택/조회한 zone에 대해서만 로그를 남기는 것을 고려하세요:

-        if (size == 1 && !recommendResponseDtoList.isEmpty()) {
-            Long zoneId = recommendResponseDtoList.get(0).zoneId();
-
-            boolean logExist = recommendLogDocumentRepository.existsByUserIdAndZoneId(userId, zoneId);
-            if (logExist) {
-                updateRecommendLog(userId, zoneId);
-            } else {
-                saveRecommendLog(userId, zoneId);
-            }
-        }
+        // 옵션 1: 모든 추천에 대해 로그 저장
+        for (WaybleZoneRecommendResponseDto dto : recommendResponseDtoList) {
+            saveOrUpdateRecommendLog(userId, dto.zoneId());
+        }
+        
+        // 옵션 2: 별도의 API로 사용자 선택 시 로그 저장

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/explore/service/WaybleZoneRecommendService.java
around lines 46 to 56, the current logic only saves recommendation logs when the
size is exactly 1, which causes missing logs for multiple recommendations.
Modify the code to iterate over all recommended zones in
recommendResponseDtoList and for each zone, check if a log exists; if yes,
update it, otherwise save a new log. This ensures consistent logging for all
recommended zones or alternatively, adjust to log only for zones the user
actually selects or views.

Comment on lines +91 to 99
public void updateRecommendLog(Long userId, Long zoneId) {
RecommendLogDocument recommendLogDocument = recommendLogDocumentRepository.findByUserIdAndZoneId(userId, zoneId)
.orElseThrow(() -> new ApplicationException(RecommendErrorCase.RECOMMEND_LOG_NOT_EXIST));

Long recommendCount = recommendLogDocument.getRecommendCount() + 1;
recommendLogDocument.updateRecommendLog(LocalDate.now(), recommendCount);

recommendLogDocumentRepository.save(recommendLogDocument);
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

동시성 문제 가능성

updateRecommendLog 메서드에서 추천 횟수를 증가시킬 때 동시성 문제가 발생할 수 있습니다. 여러 요청이 동시에 처리될 경우 일부 카운트가 손실될 수 있습니다.

Elasticsearch의 optimistic locking이나 atomic update를 사용하는 것을 권장합니다:

또는 데이터베이스 레벨에서 atomic update를 수행하도록 수정하세요.


🏁 Script executed:

#!/bin/bash
# Elasticsearch의 버전 관리 기능 사용 여부 확인
ast-grep --pattern 'class RecommendLogDocument {
  $$$
}'

Length of output: 59


동시성 문제 검증을 위해 RecommendLogDocument 클래스 정의 위치와 Elasticsearch의 optimistic locking(@Version) 사용 여부를 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
# RecommendLogDocument 클래스 검색
rg "class RecommendLogDocument" -n

# @Version 어노테이션 사용 여부 검색
rg "@Version" -n src

Length of output: 169


동시성 이슈 방지: Elasticsearch Optimistic Locking 또는 Atomic Update 적용 필요

현재 updateRecommendLog 메서드는 read-modify-write 방식으로 recommendCount를 증가시켜 여러 요청이 동시 처리될 때 카운트 손실이 발생할 수 있습니다.
다음 중 하나를 적용해 주세요:

  • RecommendLogDocument 엔티티에 버전 관리를 위한 필드와 @Version 어노테이션 추가
    예)
    @Document(indexName = "recommend_logs")
    public class RecommendLogDocument {
        @Id
        private String id;
        private Long userId;
        private Long zoneId;
        private LocalDate lastUpdated;
        private Long recommendCount;
    
        @Version
        private Long version;    // 추가
    
        // …생성자/메서드…
    }
  • 또는 Elasticsearch Update API의 스크립트를 이용해 원자적 증가 처리
    예)
    UpdateRequest request = new UpdateRequest("recommend_logs", id)
        .script(new Script("ctx._source.recommendCount += params.count")
            .params(Map.of("count", 1)));
    client.update(request, RequestOptions.DEFAULT);

점검 대상:

  • src/main/java/com/wayble/server/explore/entity/RecommendLogDocument.java
  • src/main/java/com/wayble/server/explore/service/WaybleZoneRecommendService.java: updateRecommendLog
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/explore/service/WaybleZoneRecommendService.java
around lines 91 to 99, the updateRecommendLog method increments recommendCount
using a read-modify-write approach that can cause concurrency issues. To fix
this, implement optimistic locking by adding a version field annotated with
@Version in the RecommendLogDocument entity
(src/main/java/com/wayble/server/explore/entity/RecommendLogDocument.java), or
alternatively use Elasticsearch's Update API with a script to atomically
increment recommendCount. Choose one approach to ensure atomic updates and
prevent count loss during concurrent requests.


private static final String baseUrl = "/api/v1/wayble-zones/recommend";

private final Long userId = 1L;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

테스트 격리성을 위해 userId를 동적으로 생성하세요

하드코딩된 userId는 병렬 테스트 실행 시 충돌을 일으킬 수 있습니다. 각 테스트 클래스 인스턴스에 대해 고유한 값을 생성하는 것이 좋습니다.

-private final Long userId = 1L;
+private Long userId;

그리고 @BeforeAll 메서드에서:

userId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
🤖 Prompt for AI Agents
In
src/test/java/com/wayble/server/explore/WaybleZoneRecommendApiIntegrationTest.java
at line 77, the userId is currently hardcoded as a fixed value, which can cause
conflicts during parallel test execution. Change userId to be dynamically
generated for each test instance by removing the final modifier and initializing
it in a @BeforeAll method using ThreadLocalRandom.current().nextLong(1,
Long.MAX_VALUE) to ensure unique values and maintain test isolation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/test/java/com/wayble/server/explore/WaybleZoneRecommendApiIntegrationTest.java (1)

77-77: 테스트 격리성을 위해 userId를 동적으로 생성하세요

하드코딩된 userId는 병렬 테스트 실행 시 충돌을 일으킬 수 있습니다. 각 테스트 클래스 인스턴스에 대해 고유한 값을 생성하는 것이 좋습니다.

🧹 Nitpick comments (3)
src/test/java/com/wayble/server/explore/WaybleZoneRecommendApiIntegrationTest.java (3)

94-108: 추천 로그 생성 시 zone ID 분포 개선 필요

현재 무작위로 zone ID를 생성하여 일부 zone이 누락되거나 중복될 수 있습니다. 보다 균등한 분포를 위해 순차적 또는 섞인 리스트 방식을 고려해보세요.

-for (int i = 1; i <= SAMPLES / 2; i++) {
-    Long zoneId = (long) (Math.random() * SAMPLES) + 1;
+List<Long> zoneIds = new ArrayList<>();
+for (long i = 1; i <= SAMPLES; i++) {
+    zoneIds.add(i);
+}
+Collections.shuffle(zoneIds);
+for (int i = 0; i < SAMPLES / 2; i++) {
+    Long zoneId = zoneIds.get(i);

176-176: 테스트에서 System.out.println 대신 로깅 프레임워크 사용 권장

디버깅 출력을 위해 System.out.println을 사용하는 것보다 SLF4J와 같은 로깅 프레임워크를 사용하는 것이 좋습니다. 로그 레벨을 조정하여 필요시에만 출력할 수 있습니다.

+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;

+private static final Logger log = LoggerFactory.getLogger(WaybleZoneRecommendApiIntegrationTest.class);

-System.out.println("=== 웨이블존 목록 ===");
+log.debug("=== 웨이블존 목록 ===");

Also applies to: 183-184, 189-189, 192-192


341-345: makeRandomDate 메서드 개선 제안

현재 1-40일 사이의 무작위 날짜를 생성하는데, 최대 일수를 상수로 정의하면 가독성과 유지보수성이 향상됩니다.

+private static final int MAX_DAYS_AGO = 40;
+
 private LocalDate makeRandomDate() {
     Random random = new Random();
-    int daysAgo = random.nextInt(40) + 1;
+    int daysAgo = random.nextInt(MAX_DAYS_AGO) + 1;
     return LocalDate.now().minusDays(daysAgo);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8327dc and b23d589.

📒 Files selected for processing (5)
  • src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendResponseDto.java (1 hunks)
  • src/main/java/com/wayble/server/explore/entity/RecommendLogDocument.java (1 hunks)
  • src/main/java/com/wayble/server/explore/repository/RecommendLogDocumentRepository.java (1 hunks)
  • src/main/java/com/wayble/server/explore/service/WaybleZoneRecommendService.java (2 hunks)
  • src/test/java/com/wayble/server/explore/WaybleZoneRecommendApiIntegrationTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/com/wayble/server/explore/entity/RecommendLogDocument.java
  • src/main/java/com/wayble/server/explore/dto/recommend/WaybleZoneRecommendResponseDto.java
  • src/main/java/com/wayble/server/explore/repository/RecommendLogDocumentRepository.java
  • src/main/java/com/wayble/server/explore/service/WaybleZoneRecommendService.java

Copy link
Member

@seung-in-Yoo seung-in-Yoo left a comment

Choose a reason for hiding this comment

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

우와,,, 너무 고생하셨어요! 단순 궁금증인데 이 기능에 대해서 테스트 잘되나요??

@KiSeungMin
Copy link
Member Author

@seung-in-Yoo 넵! 테스트 코드 작성해서 검증했어요!!

Copy link
Member

@zyovn zyovn left a comment

Choose a reason for hiding this comment

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

수고하셨습니다 👍👍


// 전체 방문 로그를 최대 10,000건까지 조회
NativeQuery logQuery = NativeQuery.builder()
.withMaxResults(10000)
Copy link
Member

Choose a reason for hiding this comment

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

10,000건까지 조회해도 성능 상 문제 없었나요?!?

Copy link
Member Author

Choose a reason for hiding this comment

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

네 따로 성능 문제는 없더라구요! 그런데 아마 수치는 조금 조정할 것 같아요! 최근 30일 이내 기록만 조회한다던가..!

}
}

public void saveRecommendLog(Long userId, Long zoneId) {
Copy link
Member

Choose a reason for hiding this comment

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

saveRecommendLog 메서드랑 updateRecommendLog 메서드가 이 클래스에서만 쓰이는 것 같은데 추후 다른 클래스에서 쓰일 계획이 없으면 private으로 해도 좋을 것 같습니당

Copy link
Member Author

Choose a reason for hiding this comment

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

오 좋은 것 같아요! 다음 pr때 수정하겠습니다!!

@KiSeungMin KiSeungMin merged commit 73bef6e into develop Jul 18, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 feature 기능 구현 및 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants