Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.zipte.platform.server.adapter.in.web;

import com.zipte.platform.core.response.ApiResponse;
import com.zipte.platform.security.oauth2.domain.PrincipalDetails;
import com.zipte.platform.server.adapter.in.web.dto.response.*;
import com.zipte.platform.server.application.in.dashboard.DashBoardUseCase;
import com.zipte.platform.server.domain.estate.Estate;
import com.zipte.platform.server.domain.estate.EstatePrice;
import com.zipte.platform.server.domain.region.Region;
import com.zipte.platform.server.domain.region.RegionPrice;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/v1/dashboard")
@RequiredArgsConstructor
public class DashBoardApi {

private final DashBoardUseCase dashboardService;

@GetMapping("/region")
public ApiResponse<List<RegionResponse>> getMyRegion(@AuthenticationPrincipal PrincipalDetails principalDetails) {

/// 유저 아이디 추출하기 -> AOP로 한번에 처리하기?
Long userId = principalDetails.getId();

List<Region> region = dashboardService.getFavoriteRegion(userId);

List<RegionResponse> responses = RegionResponse.from(region);
return ApiResponse.ok(responses);
}
Comment on lines +28 to +37
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

사용자 ID 추출 로직의 중복을 개선해주세요.

모든 엔드포인트에서 동일한 사용자 ID 추출 로직이 반복되고 있습니다. AOP나 베이스 컨트롤러를 활용하여 중복을 제거하는 것을 고려해보세요.

AOP를 활용한 예시:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtractUserId {
}

@Aspect
@Component
public class UserIdExtractionAspect {
    @Around("@annotation(ExtractUserId)")
    public Object extractUserId(ProceedingJoinPoint joinPoint) throws Throwable {
        // PrincipalDetails에서 userId 추출하여 메서드 파라미터로 전달
    }
}
🤖 Prompt for AI Agents
In src/main/java/com/zipte/platform/server/adapter/in/web/DashBoardApi.java
around lines 27 to 36, the user ID extraction from PrincipalDetails is
duplicated across endpoints. Refactor by creating a custom annotation and an AOP
aspect that intercepts methods annotated with it, extracts the user ID from
PrincipalDetails, and injects it as a method parameter. Then update the
controller methods to use this annotation and accept the user ID directly,
removing the manual extraction code.



@GetMapping("/region/price")
public ApiResponse<List<RegionPriceResponse>> getMyRegionPrice(@AuthenticationPrincipal PrincipalDetails principalDetails) {

/// 유저 아이디 추출하기
Long userId = principalDetails.getId();

List<RegionPrice> regionPrices = dashboardService.getFavoriteRegionPrices(userId);

List<RegionPriceResponse> responses = RegionPriceResponse.from(regionPrices);

return ApiResponse.ok(responses);
}


@GetMapping("/estate")
public ApiResponse<List<EstateDetailResponse>> getMyEstate(@AuthenticationPrincipal PrincipalDetails principalDetails) {

/// 유저 아이디 추출하기
Long userId = principalDetails.getId();

List<Estate> estates = dashboardService.getFavoriteEstates(userId);

List<EstateDetailResponse> responses = EstateDetailResponse.from(estates);

return ApiResponse.ok(responses);
}

@GetMapping("/estate/price")
public ApiResponse<List<EstatePriceListResponse>> getMyEstatePrice(@AuthenticationPrincipal PrincipalDetails principalDetails) {

/// 유저 아이디 추출하기
Long userId = principalDetails.getId();

/// 서비스 계층 추출
List<EstatePrice> estatePrices = dashboardService.getFavoriteEstatePrice(userId);

List<EstatePriceListResponse> responses = EstatePriceListResponse.from(estatePrices);

return ApiResponse.ok(responses);
}

@GetMapping("/estate/myhome")
public ApiResponse<List<EstatePriceListResponse>> getMyHomeEstate(@AuthenticationPrincipal PrincipalDetails principalDetails) {

/// 유저 아이디 추출하기
Long userId = principalDetails.getId();

/// 서비스 계층 추출
List<EstatePrice> myEstatePrices = dashboardService.getMyEstatePrices(userId);

///
List<EstatePriceListResponse> responses = EstatePriceListResponse.from(myEstatePrices);

return ApiResponse.ok(responses);
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import lombok.Builder;
import lombok.Data;

import java.util.*;

@Data
@Builder
public class EstateDetailResponse {
Expand Down Expand Up @@ -34,4 +36,11 @@ public static EstateDetailResponse from(Estate estate) {
.facility(EstateFacilityResponse.from(estate))
.build();
}

public static List<EstateDetailResponse> from(List<Estate> estates) {
return estates.stream()
.map(EstateDetailResponse::from)
.toList();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.zipte.platform.server.domain.region.RegionPrice;
import lombok.Builder;

import java.util.*;

@Builder
public record RegionPriceResponse(
@JsonProperty("지역코드")
Expand All @@ -20,7 +22,6 @@ public record RegionPriceResponse(
@JsonProperty("25~30평")
String between25and30,


@JsonProperty("30평 이상")
String upper30
) {
Expand All @@ -35,8 +36,28 @@ public static RegionPriceResponse from(RegionPrice regionPrice) {
.build();
}


public static List<RegionPriceResponse> from(List<RegionPrice> regionPrices) {
return regionPrices.stream()
.map(RegionPriceResponse::from)
.toList();
}

/// 내부 함수
private static String format(Double price) {
if (price == null) return "-";
return String.format("%.0f만원", price);

long value = price.longValue(); // 만원 단위 정수화
long billion = value / 10000; // 억 단위
long rest = value % 10000; // 천만원 이하

if (billion > 0 && rest > 0) {
return String.format("%d억 %,d만원", billion, rest);
} else if (billion > 0) {
return String.format("%d억", billion);
} else {
return String.format("%,d만원", rest);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class EstateOwnerShipAdapter implements EstateOwnerShipPort {
Expand All @@ -33,4 +35,12 @@ public void deleteOwnership(Long userId, String kaptCode) {
repository.deleteByKaptCodeAndUserId(kaptCode, userId);
}

@Override
public List<EstateOwnership> loadMyOwnerships(Long userId) {

return repository.findByUserId(userId).stream()
.map(EstateOwnershipJpaEntity::toDomain)
.toList();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,11 @@ public List<Estate> loadEstatesByRegion(String region) {
.map(EstateDocument::toDomain)
.toList();
}

@Override
public List<Estate> loadEstatesByCodes(List<String> kaptCodes) {
return repository.findByKaptCodeIn(kaptCodes).stream()
.map(EstateDocument::toDomain)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,11 @@ public List<EstatePrice> loadEstatePriceByCodeAndArea(String kaptCode, double ex
public List<EstatePrice> loadAllEstatePricesBetween(String kaptCode, String from, String to) {
return List.of();
}

@Override
public List<EstatePrice> loadEstatePricesByCodes(List<String> kaptCodes) {
return repository.findByKaptCodeIn(kaptCodes).stream()
.map(EstatePriceDocument::toDomain)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ public Page<Favorite> loadUserFavoriteByType(Long userId, FavoriteType type, Pag
.map(FavoriteJpaEntity::toDomain);
}

@Override
public List<Favorite> loadUserFavoriteByType(Long userId, FavoriteType type) {
return repository.findByUserIdAndType(userId, type).stream()
.map(FavoriteJpaEntity::toDomain)
.toList();
}

@Override
public boolean checkFavoriteByUserIdAndTypeAndCode(Long userId, FavoriteType type, String code) {
if (type.equals(FavoriteType.REGION)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public List<Region> loadChildRegionsByPrefix(String prefix, String suffix) {
.toList();
}


@Override
public List<Region> loadRegionsByCodes(List<String> codes) {
return repository.findByCodeIn(codes).stream()
.map(RegionJpaEntity::toDomain)
.toList();
}

@Override
public boolean checkExistCode(String regionCode) {
return repository.existsByCode(regionCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;

Expand Down Expand Up @@ -50,6 +51,13 @@ public boolean checkRegionPriceExist(String regionCode) {
return repository.existsByRegionCode(regionCode);
}

@Override
public List<RegionPrice> loadRegionPriceByCodes(List<String> regionCodes) {
return repository.findByRegionCodeIn(regionCodes).stream()
.map(RegionPriceJpaEntity::toDomain)
.toList();
}

@Override
public void deleteRegionPriceByCode(String regionCode) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface EstateOwnershipJpaRepository extends JpaRepository<EstateOwnershipJpaEntity, Long> {

boolean existsByKaptCodeAndUserId(String kaptCode, Long userId);

void deleteByKaptCodeAndUserId(String kaptCode, Long userId);

List<EstateOwnershipJpaEntity> findByUserId(Long userId);


}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface FavoriteJpaRepository extends JpaRepository<FavoriteJpaEntity,

Page<FavoriteJpaEntity> findByUserIdAndType(Long userId, FavoriteType type, Pageable pageable);

List<FavoriteJpaEntity> findByUserIdAndType(Long userId, FavoriteType type);

Optional<FavoriteJpaEntity> findByIdAndUserId(Long id, Long userId);

boolean existsByUserIdAndKaptCode(Long userId, String kaptCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ public interface RegionJpaRepository extends JpaRepository<RegionJpaEntity, Stri

boolean existsByCode(String code);

List<RegionJpaEntity> findByCodeIn(List<String> codes);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.*;

public interface RegionPriceJpaRepository extends JpaRepository<RegionPriceJpaEntity, Long> {

Optional<RegionPriceJpaEntity> findByRegionCode(String regionCode);

List<RegionPriceJpaEntity> findByRegionCodeIn(List<String> regionCodes);

boolean existsByRegionCode(String regionCode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ public interface EstateMongoRepository extends MongoRepository<EstateDocument, S
List<EstateDocument> findByLocation(double longitude, double latitude, double radiusInRadians);

boolean existsByKaptCode(String kaptCode);

/// 코드를 바탕으로 여러개를 한번에 가져오기
List<EstateDocument> findByKaptCodeIn(List<String> kaptCodes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public interface EstatePriceMongoRepository extends MongoRepository<EstatePriceD

List<EstatePriceDocument> findALlByKaptCodeAndExclusiveArea(String kaptCode, double exclusiveArea);

List<EstatePriceDocument> findByKaptCodeIn(List<String> kaptCodes);
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

대용량 리스트 처리시 성능을 고려해주세요.

findByKaptCodeIn 메서드는 MongoDB의 $in 연산자를 사용하게 됩니다. 매개변수 리스트가 클 경우 성능 이슈가 발생할 수 있습니다.

다음 사항들을 검토해주세요:

  • kaptCodes 리스트의 최대 크기 제한 설정
  • 필요시 청크 단위로 분할 처리하는 로직 구현 고려
  • 인덱스가 kaptCode 필드에 적절히 설정되어 있는지 확인

대용량 배치 조회의 성능을 검증하기 위해 다음 스크립트를 실행해보세요:


🏁 Script executed:

#!/bin/bash
# MongoDB에서 kaptCode 필드의 인덱스 확인
rg -A 5 -B 5 "kaptCode.*index" --type java

Length of output: 44


인덱스 설정 여부를 재확인하기 위해 아래 스크립트를 실행해주세요:


🏁 Script executed:

#!/bin/bash
# EstatePriceDocument 클래스 전체 내용 확인
rg "class EstatePriceDocument" -A50 --type java

# kaptCode 필드에 @Indexed 어노테이션이 붙었는지 확인
rg "@Indexed.*kaptCode" -A3 --type java

# 복합 인덱스(@CompoundIndex) 설정 여부 확인
rg "@CompoundIndex" -A5 --type java

Length of output: 6066


kaptCode 필드에 인덱스 설정 및 대용량 조회 처리 방안 마련 필요

findByKaptCodeIn 메서드는 MongoDB의 $in 연산자를 사용하므로, kaptCode 필드에 인덱스가 없으면 대용량 조회 시 심각한 성능 저하가 발생할 수 있습니다. 또한, 한 번에 전달되는 목록 크기가 크면 메모리 및 네트워크 부하가 커질 수 있습니다.

다음 사항을 반드시 검토 및 반영해주세요:

  • EstatePriceDocument 클래스의 kaptCode 필드에 @Indexed 어노테이션 추가
  • 필요 시 @CompoundIndex를 통해 복합 인덱스 설정
  • kaptCodes 리스트에 크기 제한을 두거나, 일정 단위(예: 500개)로 청크 분할 처리 로직 구현
  • 인덱스 적용 후 MongoDB 셸(db.estate_price.getIndexes()) 또는 GUI 도구로 실제 인덱스 생성 여부 확인
  • 적용 전·후 벤치마크를 통해 조회 성능 개선 효과 검증
🤖 Prompt for AI Agents
In
src/main/java/com/zipte/platform/server/adapter/out/mongo/estate/EstatePriceMongoRepository.java
at line 13, the findByKaptCodeIn method uses MongoDB's $in operator which can
cause performance issues with large kaptCodes lists. To fix this, ensure the
kaptCode field in EstatePriceDocument class has the @Indexed annotation or a
suitable @CompoundIndex for efficient querying. Implement logic to limit the
size of kaptCodes or split it into chunks (e.g., 500 items each) before querying
to reduce memory and network load. After applying these changes, verify index
creation in MongoDB and benchmark query performance before and after.

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,43 @@
package com.zipte.platform.server.application.in.dashboard;

import com.zipte.platform.server.domain.estate.Estate;
import com.zipte.platform.server.domain.estate.EstatePrice;
import com.zipte.platform.server.domain.region.Region;
import com.zipte.platform.server.domain.region.RegionPrice;

import java.util.List;

public interface DashBoardUseCase {

/*
매매 / 관심 지역 관심 지역 - 실거래가 동향 관심 지역으로 설정한 지역의 가격 변화에 대한 알림을 받는다.
매매 / 관심 아파트 관심 아파트 - 시세 변화 관심 아파트의 가격 변화에 대한 알림을 받는다.
전체 / 맞춤형 아파트 제공 맞춤형 추천 매물 현황 추천시스템을 통해 관심 아파트로 설정한 유사 아파트를 제공한다.
나의 아파트 가격 변화 / 나의 아파트 가격 변화 조회 / 개인정보에 등록한 나의 아파트의 가격 변화를 그래프로 확인한다.
*/


/// 관심 지역 조회하기
List<Region> getFavoriteRegion(Long userId);

/// 관심 지역 가격 조회하기
List<RegionPrice> getFavoriteRegionPrices(Long userId);


/// 관심 아파트 목록 조회하기
List<Estate> getFavoriteEstates(Long userId);


/// 관심 아파트 가격 목록 조회하기
List<EstatePrice> getFavoriteEstatePrice(Long userId);


/// AI 추천 목록 조회하기


/// 주거지 인증을 한 나의 아파트 조회하기
List<EstatePrice> getMyEstatePrices(Long userId);



}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ public interface EstatePricePort {
/// 기간별 조회하기
List<EstatePrice> loadAllEstatePricesBetween(String kaptCode, String from, String to);


/// 외부 의존성
List<EstatePrice> loadEstatePricesByCodes(List<String> kaptCodes);
Comment on lines +22 to +23
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

주석을 더 명확하게 수정해주세요.

"외부 의존성"이라는 주석이 메서드의 목적을 명확히 설명하지 못합니다. 다중 코드를 이용한 배치 조회라는 목적을 반영하여 주석을 개선해주세요.

-    /// 외부 의존성
+    /// 다중 코드 기반 배치 조회하기
     List<EstatePrice> loadEstatePricesByCodes(List<String> kaptCodes);

또한 메서드 문서화에서 다음 사항들을 명시하는 것을 고려해보세요:

  • 빈 리스트 전달시 동작
  • 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
/// 외부 의존성
List<EstatePrice> loadEstatePricesByCodes(List<String> kaptCodes);
/// 다중 코드 기반 배치 조회하기
List<EstatePrice> loadEstatePricesByCodes(List<String> kaptCodes);
🤖 Prompt for AI Agents
In
src/main/java/com/zipte/platform/server/application/out/estate/EstatePricePort.java
around lines 18 to 19, replace the vague comment "외부 의존성" with a clear
description that this method performs batch retrieval of estate prices by
multiple kapt codes. Additionally, add JavaDoc to the method specifying how it
handles an empty list input, null parameters, and cases where some codes do not
exist, clarifying expected behavior in these scenarios.

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ public interface LoadEstatePort {
/// 외부에서 활용하는 포트
List<Estate> loadEstatesByRegion(String region);

List<Estate> loadEstatesByCodes(List<String> kaptCodes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.zipte.platform.server.domain.estateOwnership.EstateOwnership;

import java.util.*;

public interface EstateOwnerShipPort {

/// 저장하기
Expand All @@ -15,4 +17,7 @@ public interface EstateOwnerShipPort {
void deleteOwnership(Long userId, String kaptCode);


/// 대시보드용
List<EstateOwnership> loadMyOwnerships(Long userId);

}
Loading