From b94331b40395c94e9fe5b5def04e913847f42472 Mon Sep 17 00:00:00 2001 From: jeongminkang Date: Mon, 2 Jun 2025 14:39:30 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix(StoreController):=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=ED=98=95=EC=8B=9D=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../matzipbookserver/global/config/KakaoFeignConfig.java | 2 +- .../matzipbookserver/store/controller/StoreController.java | 2 +- .../java/com/example/matzipbookserver/store/domain/Store.java | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/matzipbookserver/global/config/KakaoFeignConfig.java b/src/main/java/com/example/matzipbookserver/global/config/KakaoFeignConfig.java index 3de7538..59ea23d 100644 --- a/src/main/java/com/example/matzipbookserver/global/config/KakaoFeignConfig.java +++ b/src/main/java/com/example/matzipbookserver/global/config/KakaoFeignConfig.java @@ -8,7 +8,7 @@ @Configuration public class KakaoFeignConfig { - @Value("${kakao.rest-api-key}") + @Value("${kakao.client-id}") private String kakaoApiKey; @Bean diff --git a/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java b/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java index f3af2c3..c922d94 100644 --- a/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java +++ b/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java @@ -21,7 +21,7 @@ public ResponseEntity> getStoreDetail( @RequestParam String storeName, @RequestParam double x, @RequestParam double y) { - return SuccessResponse.of(StoreSuccessCode.OK, storeService.getPlaceDetail(kakaoPlaceId, storeName, x, y)); + return ResponseEntity.ok(SuccessResponse.of(StoreSuccessCode.OK, storeService.getPlaceDetail(kakaoPlaceId, storeName, x, y))) ; } } diff --git a/src/main/java/com/example/matzipbookserver/store/domain/Store.java b/src/main/java/com/example/matzipbookserver/store/domain/Store.java index a8c17e5..31b94fb 100644 --- a/src/main/java/com/example/matzipbookserver/store/domain/Store.java +++ b/src/main/java/com/example/matzipbookserver/store/domain/Store.java @@ -35,6 +35,7 @@ public class Store { private Double x; //경도 private Double y; //위도 + @Builder.Default private int voteCount = 0; } From 460a7bb9d1ab61439fd33efdb5c93e7e2d9cd391 Mon Sep 17 00:00:00 2001 From: jeongminkang Date: Mon, 2 Jun 2025 16:14:55 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(StoreController):=20=EC=A3=BC=EB=B3=80?= =?UTF-8?q?=20=EB=A7=9B=EC=A7=91=20=EB=9E=AD=ED=82=B9=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/success/StoreSuccessCode.java | 4 ++- .../store/controller/StoreController.java | 20 ++++++++++++++ .../dto/StoreRankingResponseDto.java | 26 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/matzipbookserver/store/controller/dto/StoreRankingResponseDto.java diff --git a/src/main/java/com/example/matzipbookserver/global/response/success/StoreSuccessCode.java b/src/main/java/com/example/matzipbookserver/global/response/success/StoreSuccessCode.java index 40af9e5..6623937 100644 --- a/src/main/java/com/example/matzipbookserver/global/response/success/StoreSuccessCode.java +++ b/src/main/java/com/example/matzipbookserver/global/response/success/StoreSuccessCode.java @@ -9,7 +9,9 @@ @AllArgsConstructor public enum StoreSuccessCode implements SuccessCode { - OK("STORE_SEARCH_SUCCESS", HttpStatus.OK, "가게 상세 조회 성공"); + OK("STORE_SEARCH_SUCCESS", HttpStatus.OK, "가게 조회 성공"), + ; + private final String code; diff --git a/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java b/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java index c922d94..9308df4 100644 --- a/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java +++ b/src/main/java/com/example/matzipbookserver/store/controller/StoreController.java @@ -2,9 +2,12 @@ import com.example.matzipbookserver.global.response.SuccessResponse; import com.example.matzipbookserver.global.response.success.StoreSuccessCode; +import com.example.matzipbookserver.store.controller.dto.StoreRankingResponseDto; import com.example.matzipbookserver.store.controller.dto.StoreResponseDto; import com.example.matzipbookserver.store.service.StoreService; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -14,6 +17,7 @@ public class StoreController { private final StoreService storeService; + private static final int MAX_RADIUS = 10000; // 10km @GetMapping("/{kakaoPlaceId}") public ResponseEntity> getStoreDetail( @@ -24,4 +28,20 @@ public ResponseEntity> getStoreDetail( return ResponseEntity.ok(SuccessResponse.of(StoreSuccessCode.OK, storeService.getPlaceDetail(kakaoPlaceId, storeName, x, y))) ; } + + @GetMapping("/ranking") + public ResponseEntity>> getStoreRanking( + @RequestParam double x, //latitude 경도 + @RequestParam double y, //longitude 위도 + @RequestParam(required = false, defaultValue = "2000")int radius, //미터로 받기 + Pageable pageable + ) { + int minimumRadius = Math.min(radius, MAX_RADIUS); + + Page ranking = storeService.getNearStoreRanking(x, y, minimumRadius, pageable); + return ResponseEntity.ok(SuccessResponse.of(StoreSuccessCode.OK, ranking)); + } + + + } diff --git a/src/main/java/com/example/matzipbookserver/store/controller/dto/StoreRankingResponseDto.java b/src/main/java/com/example/matzipbookserver/store/controller/dto/StoreRankingResponseDto.java new file mode 100644 index 0000000..7318f25 --- /dev/null +++ b/src/main/java/com/example/matzipbookserver/store/controller/dto/StoreRankingResponseDto.java @@ -0,0 +1,26 @@ +package com.example.matzipbookserver.store.controller.dto; + +import com.example.matzipbookserver.store.domain.Store; + +public record StoreRankingResponseDto( + Long id, + String kakaoPlaceId, + String name, + String address, + double x, + double y, + int voteCount +) { + + public static StoreRankingResponseDto from(Store store) { + return new StoreRankingResponseDto( + store.getId(), + store.getKakaoPlaceId(), + store.getName(), + store.getAddress(), + store.getX(), + store.getY(), + store.getVoteCount() + ); + } +} From be2d5cf4ea40d461b4add3e7bd9fce1963c72ab3 Mon Sep 17 00:00:00 2001 From: jeongminkang Date: Mon, 2 Jun 2025 16:15:39 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat(StoreService):=20=EB=A7=9B=EC=A7=91=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/StoreRepository.java | 28 +++++++++++++++++++ .../store/service/StoreService.java | 15 ++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/main/java/com/example/matzipbookserver/store/domain/repository/StoreRepository.java b/src/main/java/com/example/matzipbookserver/store/domain/repository/StoreRepository.java index 7e5e2ee..a4c4548 100644 --- a/src/main/java/com/example/matzipbookserver/store/domain/repository/StoreRepository.java +++ b/src/main/java/com/example/matzipbookserver/store/domain/repository/StoreRepository.java @@ -1,11 +1,39 @@ package com.example.matzipbookserver.store.domain.repository; import com.example.matzipbookserver.store.domain.Store; +import feign.Param; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; public interface StoreRepository extends JpaRepository { Optional findByKakaoPlaceId(String kakaoPlaceId); + + + @Query(value = """ + SELECT *, + (6371 * acos(cos(radians(:latitude)) * cos(radians(s.y)) * + cos(radians(s.x) - radians(:longitude)) + + sin(radians(:latitude)) * sin(radians(s.y)))) AS distance + FROM store s + WHERE (6371 * acos(cos(radians(:latitude)) * cos(radians(s.y)) * + cos(radians(s.x) - radians(:longitude)) + + sin(radians(:latitude)) * sin(radians(s.y)))) <= :radius + ORDER BY s.vote_count DESC + """, countQuery = """ + SELECT COUNT(*) + FROM store s + WHERE (6371 * acos(cos(radians(:latitude)) * cos(radians(s.y)) * + cos(radians(s.x) - radians(:longitude)) + + sin(radians(:latitude)) * sin(radians(s.y)))) <= :radius + """, nativeQuery = true) + Page findNearByVoteCount(@Param("longitude") double longitude, + @Param("latitude") double latitude, + @Param("radius") double radius, + Pageable pageable); + } diff --git a/src/main/java/com/example/matzipbookserver/store/service/StoreService.java b/src/main/java/com/example/matzipbookserver/store/service/StoreService.java index f41e24c..7eb5c56 100644 --- a/src/main/java/com/example/matzipbookserver/store/service/StoreService.java +++ b/src/main/java/com/example/matzipbookserver/store/service/StoreService.java @@ -2,6 +2,7 @@ import com.example.matzipbookserver.global.exception.RestApiException; import com.example.matzipbookserver.global.response.error.StoreErrorCode; +import com.example.matzipbookserver.store.controller.dto.StoreRankingResponseDto; import com.example.matzipbookserver.store.controller.dto.StoreResponseDto; import com.example.matzipbookserver.store.domain.Store; import com.example.matzipbookserver.store.domain.repository.StoreRepository; @@ -9,6 +10,8 @@ import com.example.matzipbookserver.store.external.dto.KakaoDocument; import com.example.matzipbookserver.store.external.dto.KakaoSearchResponse; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.Optional; @@ -46,4 +49,16 @@ public StoreResponseDto getPlaceDetail(String kakaoPlaceId, String placeName, do storeRepository.save(newStore); return StoreResponseDto.from(newStore); } + + + public Page getNearStoreRanking(double longitude, double latitude, int radius, Pageable pageable) { + + double radiusKm = radius / 1000.0; //km로 변환 + + Page storePage = storeRepository.findNearByVoteCount(longitude, latitude, radiusKm, pageable); + + return storePage.map(StoreRankingResponseDto::from); + } + + } From 7f9f86118ef4763adc0d38b46a496ffcac5e548d Mon Sep 17 00:00:00 2001 From: jeongminkang Date: Mon, 2 Jun 2025 16:17:47 +0900 Subject: [PATCH 4/4] =?UTF-8?q?test:=20=EA=B0=80=EA=B2=8C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreControllerTest.java | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 src/test/java/com/example/matzipbookserver/store/controller/StoreControllerTest.java diff --git a/src/test/java/com/example/matzipbookserver/store/controller/StoreControllerTest.java b/src/test/java/com/example/matzipbookserver/store/controller/StoreControllerTest.java new file mode 100644 index 0000000..5dcd72f --- /dev/null +++ b/src/test/java/com/example/matzipbookserver/store/controller/StoreControllerTest.java @@ -0,0 +1,260 @@ +package com.example.matzipbookserver.store.controller; + + +import com.example.matzipbookserver.global.response.success.StoreSuccessCode; +import com.example.matzipbookserver.store.controller.dto.StoreRankingResponseDto; +import com.example.matzipbookserver.store.controller.dto.StoreResponseDto; +import com.example.matzipbookserver.store.domain.Store; +import com.example.matzipbookserver.store.service.StoreService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.Arrays; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) +@WebMvcTest(StoreController.class) +public class StoreControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private StoreService storeService; + + @Autowired + private WebApplicationContext webApplicationContext; + + // result for @BeforeEach + private Store store1; + private Store store2; + private StoreResponseDto storeResponseDto1; + private StoreResponseDto storeResponseDto2; + private StoreRankingResponseDto storeRankingDto1; + private StoreRankingResponseDto storeRankingDto2; + + // Controller에 정의된 MAX_RADIUS를 테스트에서도 참조하기 위함 + // 실제 Controller의 MAX_RADIUS 값과 동일하게 유지하거나, + // Controller에서 해당 상수를 public으로 변경하여 직접 참조하는 것도 방법입니다. + private static final int CONTROLLER_MAX_RADIUS = 10000; + + @BeforeEach + void setUp(RestDocumentationContextProvider restDoc) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .apply(documentationConfiguration(restDoc) + .operationPreprocessors() + .withRequestDefaults(prettyPrint()) // 요청 본문을 예쁘게 출력 + .withResponseDefaults(prettyPrint())) // 응답 본문을 예쁘게 출력 + .alwaysDo(print()) // 모든 요청/응답을 콘솔에 출력 (디버깅용) + .build(); + + + store1 = Store.builder() + .id(1L) + .kakaoPlaceId("8099717") + .categoryName("음식점 > 한식 > 해물,생선 > 복어") + .name("초원복국 대연점") + .address("부산 남구 대연동 18-8") + .roadAddress("부산 남구 황령대로492번길 30") + .phone("02-123-4567") + .x(129.105899018046) + .y(35.1377701131047) + .kakaoPlaceUrl("http://place.map.kakao.com/8099717") + .voteCount(10) + .build(); + + store2 = Store.builder() + .id(2L) + .kakaoPlaceId("1493830808") + .categoryName("음식점 > 일식 > 일본식라면") + .name("겐쇼심야라멘") + .address("부산 남구 대연동 18-8") + .roadAddress("부산 남구 용소로13번길 16") + .phone("02-123-4567") + .x(129.105899018046) + .y(35.1377701131047) + .kakaoPlaceUrl("http://place.map.kakao.com/8099717") + .voteCount(20) + .build(); + + storeResponseDto1 = StoreResponseDto.from(store1); + storeResponseDto2 = StoreResponseDto.from(store2); + storeRankingDto1 = StoreRankingResponseDto.from(store1); + storeRankingDto2 = StoreRankingResponseDto.from(store2); + + } + + + @Test + void 가게_상세_조회_성공() throws Exception { + // Given + String kakaoPlaceId = store1.getKakaoPlaceId(); + String storeName = store1.getName(); + double x = store1.getX(); + double y = store1.getY(); + + Mockito.when(storeService.getPlaceDetail(kakaoPlaceId, storeName, x, y)) + .thenReturn(storeResponseDto1); + + // When + mockMvc.perform(get("/api/store/{kakaoPlaceId}", kakaoPlaceId) + .param("storeName", storeName) + .param("x", String.valueOf(x)) + .param("y", String.valueOf(y)) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.body.code").value(StoreSuccessCode.OK.getCode())) + .andExpect(jsonPath("$.body.message").value(StoreSuccessCode.OK.getMessage())) + .andExpect(jsonPath("$.body.result.kakaoPlaceId").value(storeResponseDto1.kakaoPlaceId())) + .andExpect(jsonPath("$.body.result.name").value(storeResponseDto1.name())) + .andExpect(jsonPath("$.body.result.address").value(storeResponseDto1.address())) + .andExpect(jsonPath("$.body.result.roadAddress").value(storeResponseDto1.roadAddress())) + .andExpect(jsonPath("$.body.result.phone").value(storeResponseDto1.phone())) + .andExpect(jsonPath("$.body.result.x").value(storeResponseDto1.x())) + .andExpect(jsonPath("$.body.result.y").value(storeResponseDto1.y())) + .andExpect(jsonPath("$.body.result.kakaoPlaceUrl").value(storeResponseDto1.kakaoPlaceUrl())) + .andDo(document("store/get-store-detail-success", // 스니펫 이름 명확히 + pathParameters( + parameterWithName("kakaoPlaceId").description("카카오 장소 ID") + ), + queryParameters( // Spring REST Docs 3.0 부터는 requestParameters() 로 통합될 수 있음 + parameterWithName("storeName").description("가게 이름"), + parameterWithName("x").description("경도 (Longitude)"), + parameterWithName("y").description("위도 (Latitude)") + ), + responseFields( + fieldWithPath("headers").type(JsonFieldType.OBJECT).description("응답 헤더 정보 (현재 구현에서는 비어 있음)").optional(), + fieldWithPath("statusCode").type(JsonFieldType.STRING).description("HTTP 상태 코드 문자열 (예: OK)"), + fieldWithPath("statusCodeValue").type(JsonFieldType.NUMBER).description("HTTP 상태 코드 숫자 (예: 200)"), + fieldWithPath("body.code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("body.message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("body.result.kakaoPlaceId").type(JsonFieldType.STRING).description("카카오 장소 ID"), + fieldWithPath("body.result.name").type(JsonFieldType.STRING).description("가게 이름"), + fieldWithPath("body.result.address").type(JsonFieldType.STRING).description("지번 주소"), + fieldWithPath("body.result.roadAddress").type(JsonFieldType.STRING).description("도로명 주소"), + fieldWithPath("body.result.phone").type(JsonFieldType.STRING).description("전화번호"), + fieldWithPath("body.result.x").type(JsonFieldType.NUMBER).description("경도 (Longitude)"), + fieldWithPath("body.result.y").type(JsonFieldType.NUMBER).description("위도 (Latitude)"), + fieldWithPath("body.result.kakaoPlaceUrl").type(JsonFieldType.STRING).description("카카오 장소 상세 URL") + ) + )); + + } + + + @Test + void 가게_랭킹_조회_성공() throws Exception{ + // Given + double x = 127.12; + double y = 35.13; + int radius = 2000; + //Pageable pageable = PageRequest.of(0, 2); // 테스트를 위해 size를 작게 조정 + Pageable pageable = PageRequest.of(0,10); + // 랭킹 결과는 voteCount가 높은 store2가 먼저 오도록 설정 + List rankingList = Arrays.asList(storeRankingDto2, storeRankingDto1); + Page storeRankingPage = new PageImpl<>(rankingList, pageable, rankingList.size()); + + Mockito.when(storeService.getNearStoreRanking(eq(x), eq(y), eq(radius), any(Pageable.class))) + .thenReturn(storeRankingPage); + + // When & Then + mockMvc.perform(get("/api/store/ranking") + .param("x", String.valueOf(x)) + .param("y", String.valueOf(y)) +// .param("radius", String.valueOf(radius)) +// .param("page", String.valueOf(pageable.getPageNumber())) +// .param("size", String.valueOf(pageable.getPageSize())) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.body.code").value(StoreSuccessCode.OK.getCode())) + .andExpect(jsonPath("$.body.message").value(StoreSuccessCode.OK.getMessage())) + .andExpect(jsonPath("$.body.result.content[0].name").value(storeRankingDto2.name())) // 첫번째 결과는 store2 + .andExpect(jsonPath("$.body.result.content[0].voteCount").value(storeRankingDto2.voteCount())) + .andExpect(jsonPath("$.body.result.content[1].name").value(storeRankingDto1.name())) // 두번째 결과는 store1 + .andExpect(jsonPath("$.body.result.content[1].voteCount").value(storeRankingDto1.voteCount())) + .andExpect(jsonPath("$.body.result.totalPages").value(1)) + .andExpect(jsonPath("$.body.result.totalElements").value(2)) + .andExpect(jsonPath("$.body.result.number").value(pageable.getPageNumber())) + .andExpect(jsonPath("$.body.result.size").value(pageable.getPageSize())) + .andDo(document("store/get-store-ranking-success", + queryParameters( + parameterWithName("x").description("중심 경도 (Longitude)"), + parameterWithName("y").description("중심 위도 (Latitude)"), + parameterWithName("radius").description("검색 반경 (미터 단위, 기본값 2000, 최대 10000)").optional(), + parameterWithName("page").description("페이지 번호 (0부터 시작)").optional(), + parameterWithName("size").description("페이지 당 항목 수").optional() + ), + responseFields( + fieldWithPath("headers").type(JsonFieldType.OBJECT).description("응답 헤더 정보 (현재 구현에서는 비어 있음)").optional(), + fieldWithPath("statusCode").type(JsonFieldType.STRING).description("HTTP 상태 코드 문자열 (예: OK)"), + fieldWithPath("statusCodeValue").type(JsonFieldType.NUMBER).description("HTTP 상태 코드 숫자 (예: 200)"), + fieldWithPath("body.code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("body.message").type(JsonFieldType.STRING).description("응답 메시지"), + // Page Content + fieldWithPath("body.result.content[]").type(JsonFieldType.ARRAY).description("가게 랭킹 목록"), + fieldWithPath("body.result.content[].id").type(JsonFieldType.NUMBER).description("가게 내부 ID"), + fieldWithPath("body.result.content[].kakaoPlaceId").type(JsonFieldType.STRING).description("카카오 장소 ID"), + fieldWithPath("body.result.content[].name").type(JsonFieldType.STRING).description("가게 이름"), + fieldWithPath("body.result.content[].address").type(JsonFieldType.STRING).description("지번 주소"), + fieldWithPath("body.result.content[].x").type(JsonFieldType.NUMBER).description("경도 (Longitude)"), + fieldWithPath("body.result.content[].y").type(JsonFieldType.NUMBER).description("위도 (Latitude)"), + fieldWithPath("body.result.content[].voteCount").type(JsonFieldType.NUMBER).description("득표 수"), + // Pageable information + fieldWithPath("body.result.pageable.pageNumber").type(JsonFieldType.NUMBER).description("현재 페이지 번호"), + fieldWithPath("body.result.pageable.pageSize").type(JsonFieldType.NUMBER).description("페이지 크기"), + fieldWithPath("body.result.pageable.sort.sorted").type(JsonFieldType.BOOLEAN).description("정렬 여부"), + fieldWithPath("body.result.pageable.sort.unsorted").type(JsonFieldType.BOOLEAN).description("미정렬 여부"), + fieldWithPath("body.result.pageable.sort.empty").type(JsonFieldType.BOOLEAN).description("정렬 정보 없음 여부"), + fieldWithPath("body.result.pageable.offset").type(JsonFieldType.NUMBER).description("페이지 오프셋"), + fieldWithPath("body.result.pageable.paged").type(JsonFieldType.BOOLEAN).description("페이징 여부"), + fieldWithPath("body.result.pageable.unpaged").type(JsonFieldType.BOOLEAN).description("미페이징 여부"), + // Page meta result + fieldWithPath("body.result.last").type(JsonFieldType.BOOLEAN).description("마지막 페이지 여부"), + fieldWithPath("body.result.totalPages").type(JsonFieldType.NUMBER).description("총 페이지 수"), + fieldWithPath("body.result.totalElements").type(JsonFieldType.NUMBER).description("총 요소 수"), + fieldWithPath("body.result.size").type(JsonFieldType.NUMBER).description("현재 페이지의 크기"), + fieldWithPath("body.result.number").type(JsonFieldType.NUMBER).description("현재 페이지 번호 (0부터 시작)"), + fieldWithPath("body.result.sort.sorted").type(JsonFieldType.BOOLEAN).description("정렬 여부"), + fieldWithPath("body.result.sort.unsorted").type(JsonFieldType.BOOLEAN).description("미정렬 여부"), + fieldWithPath("body.result.sort.empty").type(JsonFieldType.BOOLEAN).description("정렬 정보 없음 여부"), + fieldWithPath("body.result.first").type(JsonFieldType.BOOLEAN).description("첫 페이지 여부"), + fieldWithPath("body.result.numberOfElements").type(JsonFieldType.NUMBER).description("현재 페이지의 실제 요소 수"), + fieldWithPath("body.result.empty").type(JsonFieldType.BOOLEAN).description("결과 없음 여부") + ) + )); + + } + + +}