Skip to content

Conversation

@daye200
Copy link
Collaborator

@daye200 daye200 commented May 20, 2025

#80

Summary by CodeRabbit

  • 신규 기능

    • 장소 데이터(카카오/네이버 기반) 동기화, 저장, 크롤링 및 관리 기능이 추가되었습니다.
    • 장소 API 엔드포인트(/api/place/refresh)를 통해 장소 데이터 갱신이 가능합니다.
    • 장소 관련 엔터티, 서비스, 레포지토리, DTO가 새롭게 도입되었습니다.
    • 카카오 API 연동을 위한 WebClient 설정이 추가되었습니다.
    • 카카오 장소 상세 정보를 크롤링하는 기능이 도입되었습니다.
  • 문서화

    • 회원 탈퇴 API의 OpenAPI 문서가 간결하게 개선되었습니다.
  • 버그 수정

    • CORS 설정이 모든 오리진을 허용하도록 간소화되었습니다.
  • 기타

    • 서버 초기화 시 장소 데이터가 비어있을 경우 자동으로 갱신됩니다.
    • Spring 비동기 기능이 활성화되었습니다.
    • 외부 API, Selenium, WebClient 등 다양한 의존성이 추가되었습니다.

daye200 added 15 commits May 12, 2025 17:40
@coderabbitai
Copy link

coderabbitai bot commented May 20, 2025

"""

Walkthrough

이 변경사항은 Kakao API 기반의 장소 데이터 동기화 및 크롤링, 데이터베이스 저장, 그리고 관련 API 엔드포인트를 추가하는 대규모 기능 확장입니다. WebClient, Selenium, OkHttp, Gson 등 다양한 라이브러리 의존성이 추가되었고, 새로운 엔티티, 서비스, DTO, 컨트롤러가 도입되었습니다. 서버 초기화 시 장소 데이터가 비어있으면 자동으로 동기화가 트리거됩니다.

Changes

파일/경로 변경 요약
build.gradle WebDriverManager, Spring WebFlux, Gson, Selenium, Netty DNS 등 다수의 라이브러리 의존성 추가
.../config/CorsConfig.java CORS 허용 origin을 와일드카드("*")로 단순화, 주석 코드 제거
.../config/TaskExecutorConfig.java @EnableAsync 어노테이션 추가로 비동기 메서드 실행 활성화
.../config/WebClientConfig.java Kakao API용 WebClient 빈 등록 및 API Key 헤더 설정
.../controller/AuthController.java 회원 탈퇴 API의 OpenAPI 문서 설명 간소화
.../controller/PlaceController.java 장소 데이터 갱신용 POST /api/place/refresh 엔드포인트 추가, PlaceService 의존성 주입
.../model/constant/CategoryType.java Kakao 카테고리 코드 기반의 장소 분류 enum 추가, 코드 매칭 메서드 포함
.../model/postgres/Place.java Kakao/Naver 기반 장소 엔티티 신설, 다양한 장소 속성 필드 및 메타데이터 필드 추가
.../repository/PlaceRepository.java Place 엔티티용 JPA Repository 인터페이스 추가
.../service/PlaceService.java Kakao API/크롤러 기반 장소 동기화, enrich, 비동기 초기화 등 주요 서비스 로직 추가
.../service/PlaceFetchService.java Kakao API에서 장소 비동기 페이징 조회 및 엔티티 변환 서비스 추가
.../service/crawler/KakaoCrawler.java Selenium+OkHttp 조합의 Kakao 상세정보 크롤러 서비스 추가
.../model/dto/KakaoPlaceCrawlDetail.java
...KakaoPlaceDocumentResponse.java
...KakaoPlaceResponse.java
Kakao API/크롤러 응답용 DTO 클래스 3종 추가
.../util/init/FreematesInitiation.java 서버 초기화 시 placeService.refreshPlacesIfEmpty() 호출 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PlaceController
    participant PlaceService
    participant PlaceFetchService
    participant KakaoCrawler
    participant PlaceRepository
    participant Kakao API

    User->>PlaceController: POST /api/place/refresh
    PlaceController->>PlaceService: deleteAllAndRefresh()
    PlaceService->>PlaceRepository: deleteAll()
    PlaceService->>PlaceFetchService: fetchPlaces()
    PlaceFetchService->>Kakao API: GET /v2/local/search/category.json (여러 카테고리/페이지)
    Kakao API-->>PlaceFetchService: 장소 리스트 반환
    PlaceFetchService-->>PlaceService: KakaoPlaceDocumentResponse 리스트 반환
    PlaceService->>PlaceRepository: saveAll(Place 엔티티 리스트)
    loop 각 Place
        PlaceService->>KakaoCrawler: crawlByPlaceId(placeId)
        KakaoCrawler->>Kakao API: Selenium+OkHttp로 상세정보 크롤링
        Kakao API-->>KakaoCrawler: 상세정보 JSON 반환
        KakaoCrawler-->>PlaceService: KakaoPlaceCrawlDetail 반환
        PlaceService->>PlaceRepository: save(업데이트된 Place)
    end
    PlaceService-->>PlaceController: 완료 응답
    PlaceController-->>User: HTTP 200 OK
Loading

Possibly related PRs

Poem

🐰
새로운 장소를 찾아서
토끼는 코드를 달려요
카카오와 셀레니움 손잡고
세종대 근처 맛집, 카페, 놀이터
데이터 한가득 저장해요
봄바람처럼 신나는 기능,
프리메이츠에 활력을!

"""


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71f3504 and 00b80a0.

📒 Files selected for processing (1)
  • src/main/java/jombi/freemates/controller/AuthController.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/jombi/freemates/controller/AuthController.java
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.

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.

@github-actions
Copy link

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

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: 9

🧹 Nitpick comments (16)
src/main/java/jombi/freemates/controller/AuthController.java (1)

152-158: 인증 및 쿼리 파라미터 설명 개선 👍
회원 탈퇴 API에 JWT 인증 요구사항과 hard 파라미터 설명이 명확하게 추가되었습니다.
다만, 기존 AuthController 내 다른 메서드의 OpenAPI 문서와 들여쓰기·마크다운 스타일(예: - 앞 공백 수)이 일관되도록 조정 부탁드립니다.

build.gradle (1)

106-108: JSON 라이브러리 통일 제안
Gson 의존성을 추가하셨는데, Jackson 기반의 직렬화 설정과 충돌할 수 있습니다. JSON 처리 전략을 통일하거나 설정을 분리하는 리팩토링을 고려해보세요.

src/main/java/jombi/freemates/config/TaskExecutorConfig.java (1)

8-10: @EnableAsync로 비동기 실행 활성화
비동기 작업 실행을 위해 @EnableAsync를 추가하신 점이 적절합니다. 다만 corePoolSize가 기본값(1)으로 동작하므로 실제 부하에 맞춰 corePoolSize 등을 세부 설정하는 것을 권장드립니다.

src/main/java/jombi/freemates/config/WebClientConfig.java (1)

13-22: Kakao WebClient 빈 정의
baseUrl 및 인증 헤더 설정이 적절합니다. 네트워크 지연이나 오류에 대비해 .clientConnector()를 통한 타임아웃 설정이나 재시도 전략을 추가하는 옵션을 고려해보세요.

src/main/java/jombi/freemates/model/dto/KakaoCrawlDetail.java (1)

1-22: Lombok 기반 DTO 구현
Lombok을 이용해 보일러플레이트를 제거하고 가독성을 높인 점이 좋습니다. OpenAPI 문서화를 위해 @Schema 어노테이션 적용을 고려해보세요.

src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (2)

22-27: 애플리케이션 시작 시 데이터 초기화 로직 구현 👍

ApplicationReadyEvent 이벤트 리스너를 통해 애플리케이션이 시작될 때 장소 데이터를 초기화하는 로직이 잘 구현되어 있습니다. 이 방식은 애플리케이션 시작 시 필요한 데이터를 준비하는 적절한 방법입니다.

다만, 현재 placeRepository는 로깅 목적으로만 사용되고 있습니다. 이 책임을 서비스 계층으로 이동하는 것이 좋겠습니다.

@EventListener(ApplicationReadyEvent.class)
public void onAppReady() {
-  log.debug("장소 개수 {}", placeRepository.count());
  placeService.refreshPlacesIfEmpty();
}

29-32: 불필요한 공백 라인 제거

코드 가독성을 위해 파일 끝의 불필요한 공백 라인을 제거해주세요.

  }

-

-

-

}
src/main/java/jombi/freemates/model/dto/KakaoResponse.java (1)

20-25: Meta 클래스에 Setter 추가 고려

Meta 내부 클래스에는 @Getter만 적용되어 있고 @Setter가 없습니다. 이는 의도적인 설계일 수 있지만, 만약 meta 정보를 수정해야 하는 경우 문제가 될 수 있습니다. 필요에 따라 @Setter를 추가하거나, 불변 객체로 사용할 의도라면 @NoArgsConstructor@AllArgsConstructor를 추가하는 것이 좋겠습니다.

-  @Getter
+  @Getter
+  @Setter
+  @NoArgsConstructor
+  @AllArgsConstructor
  public static class Meta {
    private int totalCount;
    private int pageableCount;
    private boolean isEnd;
  }
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)

49-50: 문자 리터럴 결합 가독성 개선 제안

baseUrl + '/' + placeId 표현은 동작에는 문제없지만,
'/'charString 으로 암묵 변환되어 읽기 어려울 수 있습니다.

-String mainUrl = baseUrl+'/' + placeId;
+String mainUrl = baseUrl + "/" + placeId;
src/main/java/jombi/freemates/model/postgres/Place.java (1)

32-45: 좌표·거리 타입을 String 으로 저장하면 후처리 비용 증가

x, y, distance 가 모두 String 이라서

  • 정렬/범위검색(반경검색) 시 매번 형 변환 필요
  • 잘못된 형식의 값이 저장되어도 컴파일 시점에 검증 불가

BigDecimal(좌표)·Integer(distance, m 단위) 등 명확한 타입으로 맵핑하면 JPA 쿼리 최적화와 데이터 무결성에 유리합니다.

src/main/java/jombi/freemates/model/dto/KakaoDocument.java (1)

35-42: 좌표·거리 필드 타입 검토 권장

DTO 단계이므로 문자열 유지가 편할 수 있으나,
서비스 레이어에서 수치 연산(예: 반경 필터링)을 한다면 변환 비용이 반복 발생합니다.
필요 시 double x, double y, int distance 로 직접 매핑하도록 고민해 보세요.

src/main/java/jombi/freemates/service/PlaceService.java (5)

55-63: 카카오 API 호출 병렬화 고려 제안

concatMap 으로 인해 카테고리 → 코드 → 페이지 조회가 모두 순차적으로 진행되어 전체 수집 시간이 길어질 수 있습니다.
flatMap 에 제한된 concurrency 값을 주어 병렬 요청을 허용하면 응답 지연을 크게 줄일 수 있습니다.

-                // 코드 하나당 전체 페이지를 조회
-                .concatMap(this::fetchAllPagesFor)
+                // 코드 하나당 전체 페이지를 (최대 4개까지) 병렬 조회
+                .flatMap(this::fetchAllPagesFor, /* concurrency */ 4)

방화벽 / 쿼터 이슈가 없다면 병렬화를 적극 검토해 주세요.


64-70: 중복 필터 로직 재검토

이미 categoryType.getKakaoCodes() 를 반복하며 조회하기 때문에
filter(doc -> CATEGORIES.stream() …) 로 카테고리 그룹 코드를 다시 확인하는 과정이 사실상 중복입니다.
이 부분을 제거하면 불필요한 스트림 연산을 한 단계 줄일 수 있습니다.


150-156: 블로킹 호출의 타임아웃 및 예외 처리 강화 제안

fetchPlaces().timeout(Duration.ofMinutes(2)).block(); 에서 타임아웃 발생 시 바로 TimeoutException 이 던져져
전체 트랜잭션이 롤백됩니다.
• 예외를 캐치해 재시도하거나 사용자 친화적인 메시지로 전환
blockOptional().orElse(Collections.emptyList()) 와 같이 null-safety 확보
를 고려해 주세요.


192-206: N+1 저장 호출 → 일괄 저장으로 변경 권장

동기 크롤링 루프마다 placeRepository.save(place) 를 호출하면 DB 라운드트립이 수백 / 수천 번 발생합니다.
최종 결과를 리스트에 모아 saveAll 한 번으로 처리하거나 batch size 를 조절해 성능을 개선할 수 있습니다.


214-244: 메서드 주석과 구현 내용 불일치 & 트랜잭션 범위 검토

  1. 주석에 “네이버 크롤링” 이라고 적혀 있으나, 실제로는 KakaoCrawler 를 사용하고 있습니다. 혼동을 방지하기 위해 주석을 수정해 주세요.
  2. placeRepository.saveAll(updatedPlaces) 가 메서드 외부 트랜잭션 없이 호출됩니다. 대량 저장 시 실패 원자성을 확보하려면 @Transactional 추가를 고려해 주세요.
  3. 본 메서드는 논블로킹을 취지로 작성되었지만 마지막에 .block() 으로 다시 블로킹됩니다. 필요하다면 상위 호출부에서 block 하거나, subscribe() 로 완전히 비동기로 전환하는 것이 더 일관됩니다.
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae7861 and 58be40e.

📒 Files selected for processing (15)
  • build.gradle (3 hunks)
  • src/main/java/jombi/freemates/config/CorsConfig.java (0 hunks)
  • src/main/java/jombi/freemates/config/TaskExecutorConfig.java (1 hunks)
  • src/main/java/jombi/freemates/config/WebClientConfig.java (1 hunks)
  • src/main/java/jombi/freemates/controller/AuthController.java (1 hunks)
  • src/main/java/jombi/freemates/controller/PlaceController.java (1 hunks)
  • src/main/java/jombi/freemates/model/constant/CategoryType.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/KakaoCrawlDetail.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/KakaoDocument.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/KakaoResponse.java (1 hunks)
  • src/main/java/jombi/freemates/model/postgres/Place.java (1 hunks)
  • src/main/java/jombi/freemates/repository/PlaceRepository.java (1 hunks)
  • src/main/java/jombi/freemates/service/PlaceService.java (1 hunks)
  • src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1 hunks)
  • src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/jombi/freemates/config/CorsConfig.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (1)
src/main/java/jombi/freemates/service/PlaceService.java (1)
  • Slf4j (27-250)
src/main/java/jombi/freemates/model/dto/KakaoDocument.java (1)
src/main/java/jombi/freemates/model/dto/KakaoResponse.java (2)
  • JsonNaming (11-26)
  • Getter (20-25)
src/main/java/jombi/freemates/service/PlaceService.java (2)
src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (1)
  • Slf4j (11-32)
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)
  • Service (29-154)
🔇 Additional comments (15)
src/main/java/jombi/freemates/controller/AuthController.java (1)

159-165: '동작' 섹션 설명 보강으로 이해도↑
soft delete와 hard delete 동작을 구분해 상세히 기술한 점이 좋습니다. 변경된 동작 설명은 명확하며 추가 검토 필요 없습니다.

build.gradle (3)

32-34: WebDriverManager 의존성 추가
패키지 버전을 명시하여 안정적으로 WebDriverManager를 사용할 수 있도록 한 점이 적절합니다.


71-73: WebFlux 의존성 중복 확인 요청
Spring MVC(spring-boot-starter-web)와 함께 WebFlux를 추가하면 Netty 서버가 함께 구동될 수 있습니다. 의도한 설정인지 확인하세요.


126-128: macOS 전용 DNS 레졸버 검증 요청
현재 macOS용 네이티브 DNS 레졸버만 추가되어 있습니다. CI/CD 및 Linux 환경 지원 여부를 확인하시고, 필요 시 Linux 의존성도 함께 추가해주세요.

src/main/java/jombi/freemates/repository/PlaceRepository.java (1)

1-8: JpaRepository 상속 인터페이스 생성
기본 CRUD와 페이징 기능을 제공하는 JpaRepository를 잘 상속하셨습니다. 추가 커스텀 쿼리가 필요할 경우 이 인터페이스에 선언하여 사용하세요.

src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (1)

11-16: 컴포넌트 생성자 주입 설계 👍

의존성 주입이 잘 설계되어 있습니다. @RequiredArgsConstructorfinal 필드를 사용한 생성자 주입 방식은 Spring에서 권장하는 방식입니다.

src/main/java/jombi/freemates/model/dto/KakaoResponse.java (2)

11-16: DTO 클래스 설계 👍

DTO 클래스 설계가 잘 되어 있습니다. Jackson의 @JsonNaming을 사용하여 snake_case JSON 속성을 자바 camelCase로 매핑하고, Lombok 어노테이션을 사용하여 보일러플레이트 코드를 줄였습니다.


17-18: Kakao API 응답 구조 매핑 구현 👍

Kakao API 응답 구조에 맞게 metadocuments 필드가 잘 정의되어 있습니다.

src/main/java/jombi/freemates/controller/PlaceController.java (2)

13-19: Swagger 문서화 및 컨트롤러 설계 👍

Swagger @Tag를 사용하여 API 문서화를 적절히 구현하고, Spring MVC 컨트롤러 패턴이 잘 적용되어 있습니다.


20-21: 권한 설정 구현 필요

주석으로 처리된 @PreAuthorize 어노테이션이 있습니다. 이는 추후 권한을 설정하겠다는 의도로 보이는데, 프로덕션 환경에 배포하기 전에 반드시 권한 설정을 구현해야 합니다. 장소 데이터를 모두 삭제하고 새로고침하는 기능은 관리자만 사용할 수 있어야 합니다.

주석을 제거하고 권한 설정을 활성화해주세요:

-//@PreAuthorize("hasRole('ROLE_ADMIN')")
-//추후 권한 설정
+@PreAuthorize("hasRole('ROLE_ADMIN')")
src/main/java/jombi/freemates/model/constant/CategoryType.java (4)

8-14: 열거형 설계 및 문서화 👍

열거형(Enum) 클래스가 잘 설계되어 있고, Kakao API 문서 링크와 함께 적절히 문서화되어 있습니다. @AllArgsConstructor@Getter를 사용하여 코드가 간결합니다.


15-20: 카테고리 정의 구현 👍

카테고리 타입과 해당 Kakao 코드가 명확하게 정의되어 있습니다. 여러 코드를 하나의 카테고리로 매핑하는 Set.of() 사용은 좋은 접근 방식입니다.


26-28: 카테고리 매칭 로직 👍

단순하고 효율적인 매칭 메서드 구현이 잘 되었습니다.


30-35:

Details

✅ Verification successful

카테고리 조회 메서드 구현 검증

of 메서드 구현은 좋지만, 중복된 Kakao 코드가 있을 경우 첫 번째 일치하는 카테고리만 반환하게 됩니다. 코드 값이 여러 카테고리에 중복되지 않는지 확인이 필요합니다.


🏁 Script executed:

#!/bin/bash
# 카테고리 간 Kakao 코드 중복 여부 확인

echo "CategoryType의 Kakao 코드 중복 검사:"
rg -A 1 "Set.of\\(" src/main/java/jombi/freemates/model/constant/CategoryType.java

Length of output: 344


카테고리 Kakao 코드 중복 검사 완료 – 중복 없음

  • CategoryType.java 에 정의된 Kakao 코드(CE7, FD6, MT1, CS2, AT4, CT1, HP8)는 서로 고유합니다.
  • of(String kakaoCode) 메서드는 첫 일치 항목을 반환하지만, 현재 중복 코드가 없어 안전하게 동작합니다.

향후 새로운 카테고리 또는 Kakao 코드 추가 시 반드시 중복 여부를 확인해 주세요.

src/main/java/jombi/freemates/service/PlaceService.java (1)

118-136: CategoryType 매핑 실패 시 NPE 가능성 확인 요청

CategoryType.of() 가 매핑에 실패하면 null 을 반환할 가능성이 있습니다.
해당 필드가 DB NOT NULL 제약이거나 이후 로직에서 null 사용을 가정하지 않는다면 NPE 가 발생할 수 있으니,
예외 처리 혹은 UNKNOWN 과 같은 기본값을 반환하도록 확인 부탁드립니다.

Comment on lines 166 to 171
## 반환값 (ResponseEntity<Void>)
- **`ResponseEntity<Void>`** (상태코드 200 OK)
## 에러 코드
- `MEMBER_NOT_FOUND`: 회원을 찾을 수 없음
"""
## 에러코드
- **`MEMBER_NOT_FOUND`**: 회원을 찾을 수 없음
"""
Copy link

@coderabbitai coderabbitai bot May 20, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

반환값 및 에러코드 보강 제안
MEMBER_NOT_FOUND 외에도 인증 실패 시 UNAUTHORIZED(401) 또는 권한 없음(FORBIDDEN 403)과 같은 에러 코드/설명 추가를 고려해주세요.
이로써 JWT 토큰 미제공·만료·잘못된 토큰 시 발생 가능한 시나리오를 문서화하면 클라이언트 사용성 및 안정성이 향상됩니다.

🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/AuthController.java around lines 166
to 171, enhance the response documentation by adding error codes and
descriptions for authentication failures such as UNAUTHORIZED (401) for missing,
expired, or invalid JWT tokens, and FORBIDDEN (403) for insufficient
permissions. Update the comments or API documentation to include these scenarios
alongside MEMBER_NOT_FOUND to improve client understanding and error handling.

Copy link
Member

Choose a reason for hiding this comment

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

무시하죠

Copy link

Choose a reason for hiding this comment

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

@Cassiiopeia 네, 알겠습니다. 해당 제안은 무시하겠습니다.

Comment on lines 116 to 119
// Selenium
implementation 'org.seleniumhq.selenium:selenium-java'
implementation 'org.seleniumhq.selenium:selenium-chrome-driver'

Copy link

@coderabbitai coderabbitai bot May 20, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Selenium 버전 명시 요구
Selenium 의존성에 버전을 지정하지 않으면 호환성 관리가 어려워질 수 있습니다. 명시적 버전 지정을 권장합니다.

다음과 같이 버전을 추가해주세요:

-    implementation 'org.seleniumhq.selenium:selenium-java'
-    implementation 'org.seleniumhq.selenium:selenium-chrome-driver'
+    implementation 'org.seleniumhq.selenium:selenium-java:4.8.1'
+    implementation 'org.seleniumhq.selenium:selenium-chrome-driver:4.8.1'
📝 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
// Selenium
implementation 'org.seleniumhq.selenium:selenium-java'
implementation 'org.seleniumhq.selenium:selenium-chrome-driver'
// Selenium
implementation 'org.seleniumhq.selenium:selenium-java:4.8.1'
implementation 'org.seleniumhq.selenium:selenium-chrome-driver:4.8.1'
🤖 Prompt for AI Agents
In build.gradle around lines 116 to 119, the Selenium dependencies are declared
without specifying versions, which can cause compatibility issues. Fix this by
adding explicit version numbers to both 'selenium-java' and
'selenium-chrome-driver' dependencies to ensure consistent and manageable
builds.

Copy link
Member

Choose a reason for hiding this comment

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

무시하죠

Copy link

Choose a reason for hiding this comment

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

@Cassiiopeia 네, 이해했습니다. 해당 제안을 무시하고 현재 Selenium 의존성 설정을 그대로 유지하겠습니다.

Comment on lines +25 to +29
@PostMapping("/refresh")
public ResponseEntity<Void> refreshPlaces() {
placeService.deleteAllAndRefresh();
return ResponseEntity.ok().build();
}
Copy link

@coderabbitai coderabbitai bot May 20, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

대량 데이터 조작에 대한 보호 메커니즘 추가

deleteAllAndRefresh() 메서드는 모든 장소 데이터를 삭제하고 다시 로드하는 리소스 집약적인 작업입니다. 이러한 작업은 실수로 여러 번 호출되면 시스템에 부담을 줄 수 있습니다.

동시 호출을 제한하거나, 작업 진행 상태를 확인하는 메커니즘을 추가하는 것이 좋겠습니다. 또한 이 작업이 현재 진행 중인지 확인하는 로직을 추가하고, 작업 완료 후 결과를 로깅하는 것을 권장합니다.

@PostMapping("/refresh")
public ResponseEntity<Void> refreshPlaces() {
+  log.info("장소 데이터 전체 새로고침 시작");
  placeService.deleteAllAndRefresh();
+  log.info("장소 데이터 전체 새로고침 완료");
  return ResponseEntity.ok().build();
}

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

🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/PlaceController.java around lines 25
to 29, the refreshPlaces method calls placeService.deleteAllAndRefresh(), which
is a resource-intensive operation that deletes and reloads all place data. To
protect against multiple concurrent calls, add a mechanism to check if the
refresh operation is already in progress and prevent overlapping executions.
Implement synchronization or a flag to track the operation status, return an
appropriate response if a refresh is ongoing, and add logging after the
operation completes to record the result and duration.

Copy link
Member

Choose a reason for hiding this comment

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

무시하죠 이거 고장났네 이거

Copy link

Choose a reason for hiding this comment

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

@Cassiiopeia 네, 이해했습니다. 해당 제안을 무시하도록 하겠습니다. 필요한 경우 다른 도움이 필요하시면 말씀해 주세요.

Comment on lines 100 to 106
try (Response res = client.newCall(req).execute()) {

if (!res.isSuccessful()) {
throw new IOException("API 호출 실패: " + res.code());
}
return JsonParser.parseString(res.body().string()).getAsJsonObject();
}
Copy link

@coderabbitai coderabbitai bot May 20, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

응답 바디 null 가능성 미처리 → NPE 위험

Response.body()null 이면 res.body().string() 에서 NPE 가 발생합니다.
isSuccessful() 이어도 바디가 없을 수 있으므로 안전장치를 추가해 주세요.

-      return JsonParser.parseString(res.body().string()).getAsJsonObject();
+      if (res.body() == null) {
+        throw new IOException("API 응답에 본문이 없습니다.");
+      }
+      return JsonParser.parseString(res.body().string()).getAsJsonObject();
📝 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
try (Response res = client.newCall(req).execute()) {
if (!res.isSuccessful()) {
throw new IOException("API 호출 실패: " + res.code());
}
return JsonParser.parseString(res.body().string()).getAsJsonObject();
}
try (Response res = client.newCall(req).execute()) {
if (!res.isSuccessful()) {
throw new IOException("API 호출 실패: " + res.code());
}
if (res.body() == null) {
throw new IOException("API 응답에 본문이 없습니다.");
}
return JsonParser.parseString(res.body().string()).getAsJsonObject();
}
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java around lines
100 to 106, the code does not handle the possibility that Response.body() can be
null, which can cause a NullPointerException when calling res.body().string().
To fix this, add a null check for res.body() before accessing its string
content, and handle the null case appropriately, such as throwing an IOException
or returning a default value, to ensure safe processing even when the response
body is absent.

Copy link
Member

Choose a reason for hiding this comment

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

@daye200 null point exception 날수있다는건데

그냥해도되는데 추가하려면 하슈

회사에서는 이거 nvl() 사용해서 방지하거든요?

실제 코드에 지금있음

CommonUItil.nvl(res.body(),"")

이렇게 사용하면 되거든요?

이러면 null값이라도 String "" 이렇게 반환되어서 나와요

 return JsonParser.parseString(CommonUItil.nvl(res.body(),"")).string()).getAsJsonObject();

이렇게 쓰면됨

아무튼 null인값을 참조하면 코드에서 에러나니까

null말고 empty 문자열로 변환해서 사용하는겁니다
이러면 에러가 터지지않아요

안전장치를 항상 생각해야되긴해요

어떤 기능이 작동은 하는데 빈값으로 흘러가는거랑
어떤 기능이 에러가 터져버리는건

운영상황에서 엄청 큰 차이 이기때문에

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines 101 to 110
// 실패 시 빈 response 로 대체
.onErrorResume(Throwable.class, e -> {
log.warn("카테고리 {} 페이지 {} 호출 실패: {}", categoryCode, page, e.getMessage());
return Mono.just(new KakaoResponse(null, List.of()));
})
)
// is_end=true 이면 그 페이지까지 수집 후 스트림 종료
.takeUntil(resp -> resp.getMeta() != null && resp.getMeta().isEnd())
// KaKaoResponse.documents 를 펼쳐서 Flux<KaKaoDocument> 로 반환
.flatMapIterable(KakaoResponse::getDocuments);
Copy link

@coderabbitai coderabbitai bot May 20, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

에러 페이지 처리 시 무한 조회 가능성

onErrorResume 에서 meta == null 인 빈 응답을 리턴하면
takeUntil(resp.getMeta() != null && resp.getMeta().isEnd()) 조건이 false 로 평가되어
오류가 난 페이지 이후에도 MAX_PAGE 까지 계속 호출됩니다.

-        .takeUntil(resp -> resp.getMeta() != null && resp.getMeta().isEnd())
+        // meta 가 없으면 바로 스트림 종료
+        .takeUntil(resp -> resp.getMeta() == null || resp.getMeta().isEnd())

meta == null 상황에서도 조기에 종료하도록 수정해 과도한 불필요 호출을 막아 주세요.

📝 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
// 실패 시 빈 response 로 대체
.onErrorResume(Throwable.class, e -> {
log.warn("카테고리 {} 페이지 {} 호출 실패: {}", categoryCode, page, e.getMessage());
return Mono.just(new KakaoResponse(null, List.of()));
})
)
// is_end=true 이면 그 페이지까지 수집 후 스트림 종료
.takeUntil(resp -> resp.getMeta() != null && resp.getMeta().isEnd())
// KaKaoResponse.documents 를 펼쳐서 Flux<KaKaoDocument> 로 반환
.flatMapIterable(KakaoResponse::getDocuments);
// 실패 시 빈 response 로 대체
.onErrorResume(Throwable.class, e -> {
log.warn("카테고리 {} 페이지 {} 호출 실패: {}", categoryCode, page, e.getMessage());
return Mono.just(new KakaoResponse(null, List.of()));
})
)
// is_end=true 이면 그 페이지까지 수집 후 스트림 종료
// meta 가 없으면 바로 스트림 종료
.takeUntil(resp -> resp.getMeta() == null || resp.getMeta().isEnd())
// KaKaoResponse.documents 를 펼쳐서 Flux<KaKaoDocument> 로 반환
.flatMapIterable(KakaoResponse::getDocuments);
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceService.java around lines 101 to
110, the onErrorResume returns a KakaoResponse with meta set to null, causing
the takeUntil condition to never be true and resulting in infinite page
requests. Modify the onErrorResume to return a KakaoResponse where meta is
non-null and indicates the end condition (isEnd() == true) so that takeUntil
will stop the stream early and prevent unnecessary further calls.

Copy link
Member

Choose a reason for hiding this comment

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

이건 코드봐야될것같은데 코드오류일수도??

@daye200 확인 부탁드려요

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

daye200 added 2 commits May 20, 2025 21:19
…장소db_구축' into 20250519_#80_기능추가_place_장소_관련_정보_네이버에서_크롤링2차_장소_db구축

# Conflicts:
#	src/main/java/jombi/freemates/service/PlaceService.java
#	src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java
Copy link
Member

@Cassiiopeia Cassiiopeia left a comment

Choose a reason for hiding this comment

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

구조 바꾸시고

수정할께 좀 많네요

고생하셨슈

@Builder
@NoArgsConstructor
@AllArgsConstructor
public class KakaoCrawlDetail {
Copy link
Member

Choose a reason for hiding this comment

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

어떤거 crawlDetail인지 Class명 구체적으로 부탁드립니다

// 상세설명
private String description;
// 편의시설
private String amenities;
Copy link
Member

Choose a reason for hiding this comment

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

List amenities 로해주세요

Copy link
Collaborator Author

@daye200 daye200 May 26, 2025

Choose a reason for hiding this comment

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

저런 기능을 하는 tag를 추가할거라 이름을 전반적으로 바꾸겠습니다!

introText한 줄소개
description 상세설명
tag는 우리끼리 정한 커스텀 태그

@AllArgsConstructor
public class KakaoDocument {

private String id;
Copy link
Member

Choose a reason for hiding this comment

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

이전처럼 String 바꿔주시고

다른 entity 참고해서 자동 UUID auto gernerate로해주세요

카카오에서 제공하는 id는 칼럼으로 unique=true 걸어주세요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

dto인데도요...?

Copy link
Member

Choose a reason for hiding this comment

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

아~ dto구나~ 취소~


private String description;

private String amenities;
Copy link
Member

Choose a reason for hiding this comment

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

여기도

@Slf4j
@Service
@RequiredArgsConstructor
public class PlaceService {
Copy link
Member

Choose a reason for hiding this comment

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

지금 내부로직이 좀 마음에 안듭니다

KaKaoCrawlingService 이런식으로 나눠서

그쪽에서 크롤링 로직넣고

NaverCrawlingService 해서 이런식으로 나눠서

그쪽에서 크롤링로직 넣고

PlaceService는 말 그대로

Place에 관련된 조절을 하는곳이잖아요?

여기에서는 Place를 추가하거나

Place를 삭제하거나

Place를 수정하는

CRUD 로직이 들어가는게 맞지않을까요?

Service단 분리 시켜야될것같아요

Place생성할때

각각 service 의존성가져와서 메도르로 사용하면서 Place객체 생성하고 그러는게 더 좋지않나요?

Copy link
Collaborator Author

@daye200 daye200 May 26, 2025

Choose a reason for hiding this comment

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

PlaceFetchService(api가져올때) 를 분리해 보도록 하겠습니다
kakaocrawler가 있어서 add는 place에 있어도 될거 같은데...
어떻게 생각하세요?

Copy link
Member

Choose a reason for hiding this comment

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

어...일단 해보죠

@github-actions
Copy link

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

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: 3

♻️ Duplicate comments (3)
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (2)

40-44: ChromeDriver 매번 생성으로 인한 성능 저하 (이전 리뷰와 중복)
메서드 호출마다 ChromeDriver 인스턴스를 생성·파괴하면 CPU/메모리 소모가 급증합니다.
풀링 또는 원격 WebDriver 사용을 권장드립니다.


95-102: ⚠️ Potential issue

응답 바디 null 처리 누락 → NPE 가능성
res.body() 가 null 일 때 string() 호출에서 NullPointerException 이 발생할 수 있습니다.

-      String payload = CommonUtil.nvl(res.body().string(), "");
+      if (res.body() == null) {
+        throw new IOException("API 응답에 본문이 없습니다.");
+      }
+      String payload = CommonUtil.nvl(res.body().string(), "");
src/main/java/jombi/freemates/service/PlaceService.java (1)

21-24: 서비스 분리에 대한 이전 피드백 재검토

이전 리뷰에서 언급된 서비스 분리 제안이 타당합니다. PlaceService가 현재 다음과 같은 다양한 책임을 가지고 있습니다:

  • Place CRUD 작업
  • 데이터 동기화
  • 크롤링 오케스트레이션

PlaceFetchService 분리는 이미 완료되었지만, 크롤링 로직도 별도 서비스로 분리하는 것을 고려해보세요:

@Service
public class PlaceCrawlingService {
    public void enrichPlacesWithCrawlData(List<Place> places) { ... }
    public void enrichPlaceWithCrawlData(Place place) { ... }
}

이렇게 하면 PlaceService는 순수한 도메인 로직에 집중할 수 있습니다.

🧹 Nitpick comments (5)
src/main/java/jombi/freemates/util/init/FreematesInitiation.java (2)

4-4: 불필요한 import 정리 필요
PlaceRepository 는 현재 클래스에서 사용되지 않습니다. IDE-레벨에서는 경고로 끝나지만, CI 단계에서 -Werror 옵션이 켜져 있으면 컴파일 실패의 원인이 될 수 있으므로 제거를 권장드립니다.

-import jombi.freemates.repository.PlaceRepository;

20-30: 초기화 시점 대용량 작업의 비동기 처리 부작용 검토
placeService.refreshPlacesIfEmpty() 메서드는 @Async 로 선언되어 있어 즉시 반환됩니다.
초기 데이터가 존재하지 않을 때에는 다음과 같은 문제가 발생할 수 있습니다.

  1. 서버가 “준비 완료” 로그를 찍은 뒤에도 백그라운드에서 대량의 Kakao API 호출·DB 저장이 진행돼 초기 요청이 느려질 수 있음
  2. 비동기 스레드에서 예외가 발생해도 상위 스택으로 전파되지 않아 모니터링이 누락될 수 있음

운영 안정성을 위해

  • ApplicationRunner 대신 ApplicationReadyEvent 이후 블로킹 처리, 혹은
  • 비동기 작업에 @Async + Future 반환하여 결과 확인 및 로깅
    등을 고려해 주세요.
src/main/java/jombi/freemates/model/postgres/Place.java (1)

52-54: 좌표 필드의 데이터 타입 고려사항

좌표 정보(x, y)를 String으로 저장하고 있습니다. 만약 향후 거리 계산이나 공간 쿼리가 필요하다면 Double 타입 사용을 고려해보세요.

private Double x;  // 경도
private Double y;  // 위도

다만 Kakao API 응답 형식과의 직접 매핑을 위해 의도적으로 String을 사용한 것이라면 현재 구조도 합리적입니다.

src/main/java/jombi/freemates/service/PlaceService.java (2)

36-38: Reactive 스트림 블로킹 사용 고려사항

placeFetchService.fetchPlaces().block()을 사용하여 Reactive 스트림을 블로킹하고 있습니다. 이는 Reactive의 이점을 감소시킵니다.

비동기 처리를 유지하거나 서비스 계층에서 Reactive를 완전히 제거하는 것을 고려해보세요:

// Option 1: 완전 비동기 처리
@Async("applicationTaskExecutor")
public CompletableFuture<Void> refreshPlacesAsync(boolean deleteFirst) {
    return placeFetchService.fetchPlaces()
        .timeout(Duration.ofMinutes(2))
        .map(placeFetchService::buildPlaces)
        .doOnNext(places -> {
            if (deleteFirst) {
                placeRepository.deleteAll();
            }
            placeRepository.saveAll(places);
        })
        .then()
        .toFuture();
}

89-89: 로그 메시지의 일관성 문제

로그에서 "네이버에서 크롤링한거"라고 하지만 실제로는 Kakao 크롤링을 수행하고 있습니다. 혼동을 방지하기 위해 정확한 설명으로 수정해주세요.

- log.debug("이미지 저장된 거 {} 네이버에서 크롤링한거{}", place.getImageUrl(), kakaoPlaceCrawlDetail.getImageUrl());
+ log.debug("이미지 저장된 거 {} 카카오에서 크롤링한거{}", place.getImageUrl(), kakaoPlaceCrawlDetail.getImageUrl());
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58be40e and 71f3504.

📒 Files selected for processing (12)
  • build.gradle (3 hunks)
  • src/main/java/jombi/freemates/controller/AuthController.java (1 hunks)
  • src/main/java/jombi/freemates/controller/PlaceController.java (1 hunks)
  • src/main/java/jombi/freemates/model/constant/CategoryType.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/KakaoPlaceDocumentResponse.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/KakaoPlaceResponse.java (1 hunks)
  • src/main/java/jombi/freemates/model/postgres/Place.java (1 hunks)
  • src/main/java/jombi/freemates/service/PlaceFetchService.java (1 hunks)
  • src/main/java/jombi/freemates/service/PlaceService.java (1 hunks)
  • src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1 hunks)
  • src/main/java/jombi/freemates/util/init/FreematesInitiation.java (3 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java
  • src/main/java/jombi/freemates/model/dto/KakaoPlaceDocumentResponse.java
  • src/main/java/jombi/freemates/model/dto/KakaoPlaceResponse.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • build.gradle
  • src/main/java/jombi/freemates/controller/AuthController.java
  • src/main/java/jombi/freemates/controller/PlaceController.java
  • src/main/java/jombi/freemates/model/constant/CategoryType.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/main/java/jombi/freemates/service/PlaceFetchService.java (2)
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)
  • RequiredArgsConstructor (26-151)
src/main/java/jombi/freemates/service/PlaceService.java (1)
  • Slf4j (18-104)
src/main/java/jombi/freemates/model/postgres/Place.java (2)
src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java (1)
  • Getter (9-22)
src/main/java/jombi/freemates/model/dto/KakaoPlaceResponse.java (1)
  • Getter (20-25)
src/main/java/jombi/freemates/service/PlaceService.java (3)
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)
  • RequiredArgsConstructor (26-151)
src/main/java/jombi/freemates/service/PlaceFetchService.java (1)
  • Slf4j (18-129)
src/main/java/jombi/freemates/util/init/FreematesInitiation.java (1)
  • Slf4j (14-34)
🔇 Additional comments (1)
src/main/java/jombi/freemates/model/postgres/Place.java (1)

65-66: @Builder.Default 적용이 올바르게 되었습니다.

이전 리뷰에서 지적된 @Builder.Default 누락 문제가 해결되었습니다. 이제 빌더 패턴 사용 시 likeCountviewCount가 기본값 0으로 초기화됩니다.

Also applies to: 68-69

Comment on lines +105 to +126
public List<Place> buildPlaces(List<KakaoPlaceDocumentResponse> docs) {
return docs.stream()
.map(doc -> Place.builder()
.kakaoPlaceId(doc.getId())
.addressName(doc.getAddressName())
.categoryGroupCode(doc.getCategoryGroupCode())
.phone(doc.getPhone())
.placeName(doc.getPlaceName())
.placeUrl(doc.getPlaceUrl())
.roadAddressName(doc.getRoadAddressName())
.x(doc.getX())
.y(doc.getY())
.distance(doc.getDistance())
.imageUrl(null)
.description(null)
.introText(null)
.likeCount(0L)
.viewCount(0L)
.categoryType(CategoryType.of(doc.getCategoryGroupCode()))
.build()
)
.collect(Collectors.toList());
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

CategoryType 매핑 실패 방어 로직 필요
CategoryType.of() 가 미등록 코드에 대해 IllegalArgumentException 을 던진다면 전체 빌드가 중단됩니다.
안전한 기본값(예: CategoryType.ETC) 을 주거나 Optional 로 감싸는 방식을 권장합니다.

-            .categoryType(CategoryType.of(doc.getCategoryGroupCode()))
+            .categoryType(CategoryType.safeOf(doc.getCategoryGroupCode()))
📝 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 List<Place> buildPlaces(List<KakaoPlaceDocumentResponse> docs) {
return docs.stream()
.map(doc -> Place.builder()
.kakaoPlaceId(doc.getId())
.addressName(doc.getAddressName())
.categoryGroupCode(doc.getCategoryGroupCode())
.phone(doc.getPhone())
.placeName(doc.getPlaceName())
.placeUrl(doc.getPlaceUrl())
.roadAddressName(doc.getRoadAddressName())
.x(doc.getX())
.y(doc.getY())
.distance(doc.getDistance())
.imageUrl(null)
.description(null)
.introText(null)
.likeCount(0L)
.viewCount(0L)
.categoryType(CategoryType.of(doc.getCategoryGroupCode()))
.build()
)
.collect(Collectors.toList());
public List<Place> buildPlaces(List<KakaoPlaceDocumentResponse> docs) {
return docs.stream()
.map(doc -> Place.builder()
.kakaoPlaceId(doc.getId())
.addressName(doc.getAddressName())
.categoryGroupCode(doc.getCategoryGroupCode())
.phone(doc.getPhone())
.placeName(doc.getPlaceName())
.placeUrl(doc.getPlaceUrl())
.roadAddressName(doc.getRoadAddressName())
.x(doc.getX())
.y(doc.getY())
.distance(doc.getDistance())
.imageUrl(null)
.description(null)
.introText(null)
.likeCount(0L)
.viewCount(0L)
- .categoryType(CategoryType.of(doc.getCategoryGroupCode()))
+ .categoryType(CategoryType.safeOf(doc.getCategoryGroupCode()))
.build()
)
.collect(Collectors.toList());
}
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceFetchService.java around lines 105
to 126, the call to CategoryType.of(doc.getCategoryGroupCode()) can throw
IllegalArgumentException if the code is unregistered, causing the entire build
to fail. Modify this by adding defensive logic to catch this exception and
return a safe default value like CategoryType.ETC instead, ensuring the build
continues safely without interruption.

Comment on lines +62 to +66
.doOnError(WebClientResponseException.class,
e -> log.error("카카오 API 호출 실패: {}", e.getMessage()))
.doOnError(Throwable.class,
e -> log.error("카카오 API 호출 중 오류 발생: {}", e.getMessage()));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 전파 방식 개선 제안
현재 doOnError 는 로그만 남기고 스트림을 그대로 종료시키므로, 상위에서 block() 시 예외가 그대로 전파됩니다.
서비스 기동 중 Kakao API 가 일시적으로 실패하면 전체 초기화가 중단될 위험이 있습니다.

-        .doOnError(WebClientResponseException.class,
-            e -> log.error("카카오 API 호출 실패: {}", e.getMessage()))
-        .doOnError(Throwable.class,
-            e -> log.error("카카오 API 호출 중 오류 발생: {}", e.getMessage()));
+        .onErrorResume(e -> {
+          log.error("카카오 API 호출 중단 – 빈 리스트로 대체: {}", e.getMessage());
+          return Mono.just(List.of());
+        });

이렇게 하면 실패해도 최소한 서버 기동은 유지할 수 있습니다.

📝 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
.doOnError(WebClientResponseException.class,
e -> log.error("카카오 API 호출 실패: {}", e.getMessage()))
.doOnError(Throwable.class,
e -> log.error("카카오 API 호출 중 오류 발생: {}", e.getMessage()));
}
.onErrorResume(e -> {
log.error("카카오 API 호출 중단 – 빈 리스트로 대체: {}", e.getMessage());
return Mono.just(List.of());
});
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceFetchService.java around lines 62
to 66, the current error handling with doOnError only logs errors but allows
exceptions to propagate and potentially stop the entire service initialization.
Modify the error handling to catch the exceptions and return a fallback or empty
result instead of propagating the error, ensuring that the server startup
continues even if the Kakao API call temporarily fails.

Comment on lines +76 to +94
@Transactional
public void addKakaoCrawlInfo(){
List<Place> places = placeRepository.findAll();
for (Place place : places) {
try {
// 카카오 크롤링

KakaoPlaceCrawlDetail kakaoPlaceCrawlDetail = kakaoCrawler.crawlByPlaceId(place.getKakaoPlaceId());
// Place 엔티티에 정보 업데이트
place.setImageUrl(kakaoPlaceCrawlDetail.getImageUrl());
place.setDescription(kakaoPlaceCrawlDetail.getDescription());
place.setIntroText(kakaoPlaceCrawlDetail.getIntroText());
placeRepository.save(place);
log.debug("이미지 저장된 거 {} 네이버에서 크롤링한거{}", place.getImageUrl(), kakaoPlaceCrawlDetail.getImageUrl());
} catch (Exception e) {
log.error("카카오 크롤링 중 오류 발생: {}", e.getMessage(), e);
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

성능 및 트랜잭션 범위 개선이 필요합니다.

현재 구현에는 다음과 같은 문제점들이 있습니다:

  1. 메모리 사용량: 모든 Place를 한 번에 메모리에 로드
  2. 트랜잭션 범위: 전체 크롤링 과정이 하나의 트랜잭션으로 처리되어 DB 연결이 오래 점유됨
  3. 에러 처리: 한 장소 크롤링 실패 시에도 다른 장소들은 계속 처리되지만, 트랜잭션 롤백 위험

다음과 같이 배치 처리로 개선할 것을 제안합니다:

@Transactional
public void addKakaoCrawlInfo() {
    int batchSize = 50; // 배치 크기 설정
    long totalCount = placeRepository.count();
    
    for (int i = 0; i < totalCount; i += batchSize) {
        Pageable pageable = PageRequest.of(i / batchSize, batchSize);
        List<Place> places = placeRepository.findAll(pageable).getContent();
        
        processBatch(places);
    }
}

@Transactional
private void processBatch(List<Place> places) {
    for (Place place : places) {
        try {
            KakaoPlaceCrawlDetail detail = kakaoCrawler.crawlByPlaceId(place.getKakaoPlaceId());
            updatePlaceWithCrawlInfo(place, detail);
        } catch (Exception e) {
            log.error("장소 ID {} 크롤링 실패: {}", place.getKakaoPlaceId(), e.getMessage());
            // 개별 실패는 전체 배치를 중단시키지 않음
        }
    }
}
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceService.java around lines 76 to
94, the current addKakaoCrawlInfo method loads all Place entities at once and
processes them in a single transaction, causing high memory usage and long DB
connection holding. Refactor the method to process Places in batches using
pagination (e.g., batch size 50), and move the processing of each batch into a
separate @Transactional method. Inside this batch method, handle exceptions per
Place to log errors without rolling back the entire batch. This approach reduces
memory footprint, shortens transaction scope, and improves error isolation.

@daye200 daye200 merged commit b3a6fad into main May 26, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants