Skip to content

Commit 0f4d44f

Browse files
authored
Merge pull request #28 from Sportize/feat/facility
✨ Feat: 체육시설 도메인 및 위치 기반 조회 기능 구현
2 parents b229556 + 631069c commit 0f4d44f

File tree

10 files changed

+216
-0
lines changed

10 files changed

+216
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ src/main/resources/*.yml
3838

3939
### VS Code ###
4040
.vscode/
41+
42+
## local docker compose
43+
docker/docker-compose.local.yml

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ dependencies {
5050

5151
// AWS S3
5252
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
53+
54+
// PostGIS 공간 타입 <-> JTS(Point 등) 매핑 지원
55+
implementation "org.hibernate.orm:hibernate-spatial"
56+
57+
// JTS
58+
implementation 'org.locationtech.jts:jts-core:1.19.0'
59+
5360
}
5461

5562
tasks.named('test') {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.be.sportizebe.domain.facility.controller;
2+
3+
import com.be.sportizebe.domain.facility.dto.FacilityNearResponse;
4+
import com.be.sportizebe.domain.facility.service.SportsFacilityService;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.web.bind.annotation.*;
7+
8+
import java.util.List;
9+
10+
@RestController
11+
@RequiredArgsConstructor
12+
@RequestMapping("/api/facilities")
13+
public class SportsFacilityController {
14+
15+
private final SportsFacilityService sportsFacilityService;
16+
17+
@GetMapping("/near")
18+
public List<FacilityNearResponse> near(
19+
@RequestParam double lat,
20+
@RequestParam double lng,
21+
@RequestParam(defaultValue = "3000") int radiusM,
22+
@RequestParam(defaultValue = "50") int limit,
23+
@RequestParam(required = false) String type
24+
25+
) {
26+
return sportsFacilityService.getNear(lat, lng, radiusM, limit, type);
27+
}
28+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.be.sportizebe.domain.facility.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@Builder
8+
public class FacilityNearResponse {
9+
private Long id;
10+
private String facilityName;
11+
private String introduce;
12+
private String thumbnailUrl;
13+
private String facilityType;
14+
private int distanceM; // 프론트에서 보기 쉽게 미터 int로
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.be.sportizebe.domain.facility.entity;
2+
3+
public enum FacilityType {
4+
BASKETBALL,
5+
SOCCER,
6+
BADMINTON,
7+
TENNIS,
8+
BOWLING,
9+
ETC
10+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.be.sportizebe.domain.facility.entity;
2+
3+
import com.be.sportizebe.global.common.BaseTimeEntity;
4+
import jakarta.persistence.*;
5+
import lombok.*;
6+
import org.locationtech.jts.geom.Point;
7+
8+
@Entity
9+
@Table(name = "sports_facilities")
10+
@Getter
11+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
12+
@AllArgsConstructor(access = AccessLevel.PRIVATE)
13+
@Builder
14+
public class SportsFacility extends BaseTimeEntity {
15+
@Id
16+
@GeneratedValue(strategy = GenerationType.IDENTITY)
17+
private Long id;
18+
19+
@Column(nullable = false, length = 100)
20+
private String facilityName;
21+
22+
@Column(columnDefinition = "text")
23+
private String introduce;
24+
25+
// 리소스 부담 생각해서 일단 썸네일 이미지 1장으로만 구현하는 방식
26+
private String thumbnailUrl;
27+
28+
@Enumerated(EnumType.STRING)
29+
@Column(nullable = false, length = 30)
30+
private FacilityType facilityType;
31+
32+
@Column(columnDefinition = "geography(Point, 4326)", nullable = false)
33+
private Point location;
34+
35+
// 변경 메서드(Dirty Checking용)
36+
public void changeInfo(String facilityName, String introduce, String thumbnailUrl, FacilityType facilityType) {
37+
if (facilityName != null) this.facilityName = facilityName;
38+
if (introduce != null) this.introduce = introduce;
39+
if (thumbnailUrl != null) this.thumbnailUrl = thumbnailUrl;
40+
if (facilityType != null) this.facilityType = facilityType;
41+
}
42+
43+
public void changeLocation(Point location) {
44+
this.location = location;
45+
}
46+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.be.sportizebe.domain.facility.mapper;
2+
3+
import com.be.sportizebe.domain.facility.dto.FacilityNearResponse;
4+
import com.be.sportizebe.domain.facility.repository.FacilityNearProjection;
5+
6+
public interface FacilityMapper {
7+
8+
public static FacilityNearResponse toNearResponse(FacilityNearProjection p){
9+
return FacilityNearResponse.builder()
10+
.id(p.getId())
11+
.facilityName(p.getFacilityName())
12+
.introduce(p.getIntroduce())
13+
.thumbnailUrl(p.getThumbnailUrl())
14+
.facilityType(p.getFacilityType())
15+
.distanceM((int) Math.round(p.getDistanceM()))
16+
.build();
17+
}
18+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.be.sportizebe.domain.facility.repository;
2+
3+
public interface FacilityNearProjection {
4+
Long getId();
5+
String getFacilityName();
6+
String getIntroduce();
7+
String getFacilityType();
8+
String getThumbnailUrl();
9+
Double getDistanceM();
10+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.be.sportizebe.domain.facility.repository;
2+
3+
import com.be.sportizebe.domain.facility.entity.SportsFacility;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.Query;
6+
import org.springframework.data.repository.query.Param;
7+
8+
import java.util.List;
9+
10+
public interface SportsFacilityRepository extends JpaRepository<SportsFacility, Long> {
11+
12+
@Query(value = """
13+
SELECT
14+
sf.id AS id,
15+
sf.facility_name AS facilityName,
16+
sf.introduce AS introduce,
17+
sf.thumbnail_url AS thumbnailUrl,
18+
sf.facility_type AS facilityType,
19+
ST_Distance(
20+
sf.location,
21+
ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography
22+
) AS distanceM
23+
FROM sports_facilities sf
24+
WHERE ST_DWithin(
25+
sf.location,
26+
ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography,
27+
:radiusM
28+
)
29+
AND (:type IS NULL OR sf.facility_type = :type)
30+
ORDER BY distanceM
31+
LIMIT :limit
32+
""", nativeQuery = true)
33+
List<FacilityNearProjection> findNear(
34+
@Param("lat") double lat,
35+
@Param("lng") double lng,
36+
@Param("radiusM") long radiusM,
37+
@Param("limit") int limit,
38+
@Param("type") String type
39+
);
40+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.be.sportizebe.domain.facility.service;
2+
3+
import com.be.sportizebe.domain.facility.dto.FacilityNearResponse;
4+
import com.be.sportizebe.domain.facility.mapper.FacilityMapper;
5+
import com.be.sportizebe.domain.facility.repository.FacilityNearProjection;
6+
import com.be.sportizebe.domain.facility.repository.SportsFacilityRepository;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.stereotype.Service;
9+
import org.springframework.transaction.annotation.Transactional;
10+
11+
import java.util.List;
12+
13+
@Service
14+
@RequiredArgsConstructor
15+
@Transactional(readOnly = true)
16+
public class SportsFacilityService {
17+
18+
private final SportsFacilityRepository sportsFacilityRepository;
19+
20+
public List<FacilityNearResponse> getNear(
21+
double lat,
22+
double lng,
23+
long radiusM,
24+
int limit,
25+
String type
26+
) {
27+
if (radiusM <= 0) radiusM = 1000;
28+
if (radiusM > 20000) radiusM = 20000;
29+
if (limit <= 0) limit = 50;
30+
if (limit > 200) limit = 200;
31+
32+
List<FacilityNearProjection> rows =
33+
sportsFacilityRepository.findNear(lat, lng, radiusM, limit, type);
34+
35+
return rows.stream()
36+
.map(FacilityMapper::toNearResponse)
37+
.toList();
38+
}
39+
}

0 commit comments

Comments
 (0)