Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ dependencies {
// JTS
implementation 'org.locationtech.jts:jts-core:1.19.0'

// redis
implementation "org.springframework.boot:spring-boot-starter-data-redis"
implementation "org.springframework.boot:spring-boot-starter-cache"
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.be.sportizebe.domain.facility.dto;

public interface CacheKeyProvider {
String generateCacheKey();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityMarkerRequest.java
package com.be.sportizebe.domain.facility.dto.request;

import com.be.sportizebe.domain.facility.dto.CacheKeyProvider;
import com.be.sportizebe.domain.facility.entity.FacilityType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
Expand All @@ -9,7 +10,7 @@

@Getter
@Setter
public class FacilityMarkerRequest {
public class FacilityMarkerRequest implements CacheKeyProvider {

@Schema(description = "지도 중심 위도", example = "37.2869", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "지도 중심 위도(centerLat)는 필수입니다")
Expand All @@ -35,4 +36,14 @@ public class FacilityMarkerRequest {

@Schema(description = "종목 필터(선택)", example = "SOCCER", nullable = true)
private FacilityType type;

@Override
public String generateCacheKey() {
double gridLat = Math.round(this.lat * 10000) / 10000.0;
double gridLng = Math.round(this.lng * 10000) / 10000.0;
String typeName = (type == null) ? "ALL" : type.name();

return String.format("%.4f:%.4f:%d:%d:%s",
gridLat, gridLng, radiusM, limit, typeName);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityNearRequest.java
package com.be.sportizebe.domain.facility.dto.request;

import com.be.sportizebe.domain.facility.dto.CacheKeyProvider;
import com.be.sportizebe.domain.facility.entity.FacilityType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
Expand All @@ -9,7 +10,7 @@

@Getter
@Setter
public class FacilityNearRequest {
public class FacilityNearRequest implements CacheKeyProvider {

@Schema(description = "위도", example = "37.2662", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "위도(lat)는 필수입니다")
Expand All @@ -35,4 +36,15 @@ public class FacilityNearRequest {

@Schema(description = "종목 필터(선택)", example = "BASKETBALL", nullable = true)
private FacilityType type;

@Override
public String generateCacheKey() {
// 소수점 4자리 반올림 (약 11m 격자)
double gridLat = Math.round(this.lat * 10000) / 10000.0;
double gridLng = Math.round(this.lng * 10000) / 10000.0;
String typeName = (type == null) ? "ALL" : type.name();

return String.format("%.4f:%.4f:%d:%d:%s",
gridLat, gridLng, radiusM, limit, typeName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,16 @@
import com.be.sportizebe.domain.facility.dto.request.FacilityNearRequest;
import com.be.sportizebe.domain.facility.dto.response.FacilityMarkerResponse;
import com.be.sportizebe.domain.facility.dto.response.FacilityNearResponse;
import com.be.sportizebe.domain.facility.mapper.FacilityMapper;
import com.be.sportizebe.domain.facility.repository.SportsFacilityRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class SportsFacilityService {
public interface SportsFacilityService {

private final SportsFacilityRepository sportsFacilityRepository;
List<FacilityNearResponse> getNear(FacilityNearRequest request); // 현재 위치 기준 반경 내 체육시설을 거리순으로 조히
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

주석 오탈자 수정 제안(“조히” → “조회”).

✍️ 수정 제안
-    List<FacilityNearResponse> getNear(FacilityNearRequest request); // 현재 위치 기준 반경 내 체육시설을 거리순으로 조히
+    List<FacilityNearResponse> getNear(FacilityNearRequest request); // 현재 위치 기준 반경 내 체육시설을 거리순으로 조회
📝 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<FacilityNearResponse> getNear(FacilityNearRequest request); // 현재 위치 기준 반경 내 체육시설을 거리순으로 조히
List<FacilityNearResponse> getNear(FacilityNearRequest request); // 현재 위치 기준 반경 내 체육시설을 거리순으로 조회
🤖 Prompt for AI Agents
In
`@src/main/java/com/be/sportizebe/domain/facility/service/SportsFacilityService.java`
at line 12, The inline comment on the getNear method contains a typo ("조히") — in
the SportsFacilityService interface update the comment for the method
List<FacilityNearResponse> getNear(FacilityNearRequest request) to read "현재 위치
기준 반경 내 체육시설을 거리순으로 조회" (replace "조히" with "조회"); simply correct the Korean
comment text adjacent to the getNear method declaration.

// @Param: request 위치, 반경, 종목 등의 조회 조건
// @return: 체육시설 상세 목록

public List<FacilityNearResponse> getNear(FacilityNearRequest request) {
String type = (request.getType() == null) ? null : request.getType().name();

var list = sportsFacilityRepository.findNear(
request.getLat(),
request.getLng(),
request.getRadiusM(),
request.getLimit(),
type
);

return list.stream()
.map(FacilityMapper::toNearResponse)
.toList();
}
public List<FacilityMarkerResponse> getMarkers(FacilityMarkerRequest request) {
String type = (request.getType() == null) ? null : request.getType().name(); // ✅ enum -> String

var list = sportsFacilityRepository.findMarkersNear(
request.getLat(),
request.getLng(),
request.getRadiusM(),
request.getLimit(),
type
);

return list.stream()
.map(FacilityMapper::toMarkerResponse)
.toList();
}
}
List<FacilityMarkerResponse> getMarkers(FacilityMarkerRequest request); // 지도 중심 좌표 기준 반경 내 체육시설 마커 목록 조회
// @Param: request 지도 중심 좌표, 반경, 종목 등의 조회 조건
// @return: 지도 마커용 체육시설 목록
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.be.sportizebe.domain.facility.service;

import com.be.sportizebe.domain.facility.dto.request.FacilityMarkerRequest;
import com.be.sportizebe.domain.facility.dto.request.FacilityNearRequest;
import com.be.sportizebe.domain.facility.dto.response.FacilityMarkerResponse;
import com.be.sportizebe.domain.facility.dto.response.FacilityNearResponse;
import com.be.sportizebe.domain.facility.mapper.FacilityMapper;
import com.be.sportizebe.domain.facility.repository.SportsFacilityRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class SportsFacilityServiceImpl implements SportsFacilityService {

private final SportsFacilityRepository sportsFacilityRepository;

@Override
@Cacheable(
cacheNames = "facilityNear",
key = "#root.args[0].generateCacheKey()"
)
public List<FacilityNearResponse> getNear(FacilityNearRequest request) {
String type = (request.getType() == null) ? null : request.getType().name();

return sportsFacilityRepository.findNear(
request.getLat(),
request.getLng(),
request.getRadiusM(),
request.getLimit(),
type
).stream()
.map(FacilityMapper::toNearResponse)
.toList();
}

@Override
@Cacheable(
cacheNames = "facilityMarkers",
key = "#root.args[0].generateCacheKey()"
)
public List<FacilityMarkerResponse> getMarkers(FacilityMarkerRequest request) {
String type = (request.getType() == null) ? null : request.getType().name();

return sportsFacilityRepository.findMarkersNear(
request.getLat(),
request.getLng(),
request.getRadiusM(),
request.getLimit(),
type
).stream()
.map(FacilityMapper::toMarkerResponse)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.be.sportizebe.domain.post.dto.request.CreatePostRequest;
import com.be.sportizebe.domain.post.dto.request.UpdatePostRequest;
import com.be.sportizebe.domain.post.dto.response.PostPageResponse;
import com.be.sportizebe.domain.post.dto.response.PostResponse;
import com.be.sportizebe.domain.post.entity.PostProperty;
import com.be.sportizebe.domain.post.service.PostService;
Expand Down Expand Up @@ -64,10 +65,10 @@ public ResponseEntity<BaseResponse<Void>> deletePost(

@GetMapping("/posts/{property}")
@Operation(summary = "게시글 목록 조회", description = "게시판 종류별 게시글 목록을 페이징하여 조회합니다.")
public ResponseEntity<BaseResponse<Page<PostResponse>>> getPosts(
public ResponseEntity<BaseResponse<PostPageResponse>> getPosts(
@Parameter(description = "게시판 종류 (SOCCER, BASKETBALL, FREE)") @PathVariable PostProperty property,
@Parameter(hidden = true) @PageableDefault(size = 10, sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
Page<PostResponse> response = postService.getPosts(property, pageable);
PostPageResponse response = postService.getPosts(property, pageable);
return ResponseEntity.ok(BaseResponse.success("게시글 목록 조회 성공", response));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.be.sportizebe.domain.post.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "페이지 정보")
public record PageInfoResponse(
@Schema(description = "현재 페이지 (0부터 시작)", example = "0")
int page,

@Schema(description = "페이지 크기", example = "10")
int size,

@Schema(description = "전체 요소 수", example = "123")
long totalElements,

@Schema(description = "전체 페이지 수", example = "13")
int totalPages,

@Schema(description = "다음 페이지 존재 여부", example = "true")
boolean hasNext
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.be.sportizebe.domain.post.dto.response;

import com.be.sportizebe.domain.post.entity.Post;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.data.domain.Page;

import java.util.List;

@Schema(title = "PostPageResponse", description = "게시글 목록 + 페이지 정보 응답")
public record PostPageResponse(

@Schema(description = "게시글 목록")
List<PostResponse> content,

@Schema(description = "페이지 정보")
PageInfoResponse pageInfo

) {
public static PostPageResponse from(Page<Post> page) {
return new PostPageResponse(
page.getContent().stream()
.map(PostResponse::from)
.toList(),
new PageInfoResponse(
page.getNumber(),
page.getSize(),
page.getTotalElements(),
page.getTotalPages(),
page.hasNext()
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.be.sportizebe.domain.post.dto.request.CreatePostRequest;
import com.be.sportizebe.domain.post.dto.request.UpdatePostRequest;
import com.be.sportizebe.domain.post.dto.response.PostPageResponse;
import com.be.sportizebe.domain.post.dto.response.PostResponse;
import com.be.sportizebe.domain.post.entity.PostProperty;
import com.be.sportizebe.domain.user.entity.User;
Expand All @@ -16,5 +17,5 @@ public interface PostService {

void deletePost(Long postId, User user); // 게시글 삭제

Page<PostResponse> getPosts(PostProperty property, Pageable pageable); // 게시글 목록 조회
PostPageResponse getPosts(PostProperty property, Pageable pageable); // 게시글 목록 조회
}
Loading