From 894ac7f4cc89348bb1bf2130858f939eec854683 Mon Sep 17 00:00:00 2001 From: Seogyeong Yun Date: Tue, 31 Mar 2026 08:28:34 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=9C=84=EC=B9=98=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EB=9E=9C=EB=8D=A4=20=EC=9E=A5=EC=86=8C=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Place, ServiceCategory, CategoryMapping 엔티티 생성 - 위치(lat, lng) 기반 500m 범위 조회 로직 구현 - 카테고리별 랜덤 추천(한잔2, 한입2, 한숨1, 한손1, 한눈1) 로직 추가 - PlaceRepository 랜덤 조회 쿼리 구현 (PostgreSQL RANDOM()) - RecommendationService 비즈니스 로직 구현 - RecommendationController API 추가 (/places/recommend) - 추천 결과 응답 DTO 및 도보 시간 계산 로직 구현 - data.sql 초기 데이터 삽입 설정 추가 --- .idea/compiler.xml | 25 +++++++ .idea/dataSources.xml | 17 +++++ .idea/misc.xml | 7 ++ .idea/modules.xml | 9 +++ .idea/vcs.xml | 6 ++ .../controller/RecommendationController.java | 33 +++++++++ .../dto/RecommendationResponseDTO.java | 53 ++++++++++++++ .../_team/onmyway/entity/CategoryMapping.java | 24 +++++++ .../main/java/_team/onmyway/entity/Place.java | 52 ++++++++++++++ .../_team/onmyway/entity/ServiceCategory.java | 20 ++++++ .../java/_team/onmyway/entity/SourceType.java | 7 ++ .../onmyway/exception/GlobalExceptionHandler | 4 ++ .../repository/CategoryMappingRepository.java | 12 ++++ .../onmyway/repository/PlaceRepository.java | 28 ++++++++ .../repository/ServiceCategoryRepository.java | 7 ++ .../service/RecommendationService.java | 70 +++++++++++++++++++ .../src/main/resources/application.properties | 4 ++ backend/src/main/resources/data.sql | 14 ++++ frontend/package-lock.json | 6 ++ 19 files changed, 398 insertions(+) create mode 100644 .idea/compiler.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 backend/src/main/java/_team/onmyway/controller/RecommendationController.java create mode 100644 backend/src/main/java/_team/onmyway/dto/RecommendationResponseDTO.java create mode 100644 backend/src/main/java/_team/onmyway/entity/CategoryMapping.java create mode 100644 backend/src/main/java/_team/onmyway/entity/Place.java create mode 100644 backend/src/main/java/_team/onmyway/entity/ServiceCategory.java create mode 100644 backend/src/main/java/_team/onmyway/entity/SourceType.java create mode 100644 backend/src/main/java/_team/onmyway/exception/GlobalExceptionHandler create mode 100644 backend/src/main/java/_team/onmyway/repository/CategoryMappingRepository.java create mode 100644 backend/src/main/java/_team/onmyway/repository/PlaceRepository.java create mode 100644 backend/src/main/java/_team/onmyway/repository/ServiceCategoryRepository.java create mode 100644 backend/src/main/java/_team/onmyway/service/RecommendationService.java create mode 100644 backend/src/main/resources/data.sql create mode 100644 frontend/package-lock.json diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..5d2e396 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..af7442b --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/onmyway + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..345615c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..942ad07 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/_team/onmyway/controller/RecommendationController.java b/backend/src/main/java/_team/onmyway/controller/RecommendationController.java new file mode 100644 index 0000000..4c3025e --- /dev/null +++ b/backend/src/main/java/_team/onmyway/controller/RecommendationController.java @@ -0,0 +1,33 @@ +package _team.onmyway.controller; + +import _team.onmyway.dto.RecommendationResponseDTO; +import _team.onmyway.service.RecommendationService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/places") +public class RecommendationController { + + private final RecommendationService recommendationService; + + // 전체 추천 (7개 조합) + @GetMapping("/recommend") + public RecommendationResponseDTO recommend( + @RequestParam double lat, + @RequestParam double lng + ) { + return recommendationService.recommend(lat, lng); + } + + // 카테고리 필터 (7개 전부 같은 카테고리) + @GetMapping("/recommend/category") + public RecommendationResponseDTO recommendByCategory( + @RequestParam double lat, + @RequestParam double lng, + @RequestParam Long categoryId + ) { + return recommendationService.recommendByCategory(lat, lng, categoryId); + } +} diff --git a/backend/src/main/java/_team/onmyway/dto/RecommendationResponseDTO.java b/backend/src/main/java/_team/onmyway/dto/RecommendationResponseDTO.java new file mode 100644 index 0000000..5d129ad --- /dev/null +++ b/backend/src/main/java/_team/onmyway/dto/RecommendationResponseDTO.java @@ -0,0 +1,53 @@ +package _team.onmyway.dto; + +import _team.onmyway.entity.Place; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@AllArgsConstructor +public class RecommendationResponseDTO { + + private List places; + + public static RecommendationResponseDTO from(List places, double userLat, double userLng) { + return new RecommendationResponseDTO( + places.stream() + .map(p -> PlaceInfo.from(p, userLat, userLng)) + .collect(Collectors.toList()) + ); + } + + @Getter + @AllArgsConstructor + static class PlaceInfo { + private String name; + private double lat; + private double lng; + private String category; + private int walkingMinutes; + + public static PlaceInfo from(Place p, double userLat, double userLng) { + + double distance = calculateDistance(userLat, userLng, p.getLat(), p.getLng()); + int minutes = (int) (distance / 80); // 80m = 1분 + + return new PlaceInfo( + p.getName(), + p.getLat(), + p.getLng(), + p.getServiceCategory().getName(), + minutes + ); + } + + private static double calculateDistance(double lat1, double lng1, double lat2, double lng2) { + double dx = lat1 - lat2; + double dy = lng1 - lng2; + return Math.sqrt(dx * dx + dy * dy) * 111000; + } + } +} diff --git a/backend/src/main/java/_team/onmyway/entity/CategoryMapping.java b/backend/src/main/java/_team/onmyway/entity/CategoryMapping.java new file mode 100644 index 0000000..e5c91ea --- /dev/null +++ b/backend/src/main/java/_team/onmyway/entity/CategoryMapping.java @@ -0,0 +1,24 @@ +package _team.onmyway.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class CategoryMapping { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + private SourceType source; + + private String apiCategory; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "service_category_id") + private ServiceCategory serviceCategory; +} \ No newline at end of file diff --git a/backend/src/main/java/_team/onmyway/entity/Place.java b/backend/src/main/java/_team/onmyway/entity/Place.java new file mode 100644 index 0000000..37e8015 --- /dev/null +++ b/backend/src/main/java/_team/onmyway/entity/Place.java @@ -0,0 +1,52 @@ +package _team.onmyway.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(indexes = { + @Index(name = "idx_lat_lng", columnList = "lat, lng"), + @Index(name = "idx_service_category", columnList = "service_category_id") + }) +@Getter +@NoArgsConstructor +public class Place { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 장소명 + private String name; + + private Double lat; + private Double lng; + + private String address; + + // 카카오/네이버 원본 카테고리 + private String apiCategory; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "service_category_id") + private ServiceCategory serviceCategory; + + // kakao place id / naver place id + private String apiPlaceId; + + @Enumerated(EnumType.STRING) + private SourceType source; + + private String sourceId; + + @CreationTimestamp + private LocalDateTime createdAt; + + @UpdateTimestamp + private LocalDateTime updatedAt; +} \ No newline at end of file diff --git a/backend/src/main/java/_team/onmyway/entity/ServiceCategory.java b/backend/src/main/java/_team/onmyway/entity/ServiceCategory.java new file mode 100644 index 0000000..b211e2b --- /dev/null +++ b/backend/src/main/java/_team/onmyway/entity/ServiceCategory.java @@ -0,0 +1,20 @@ +package _team.onmyway.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor +public class ServiceCategory { + + @Id + private Long id; + + private String name; +} diff --git a/backend/src/main/java/_team/onmyway/entity/SourceType.java b/backend/src/main/java/_team/onmyway/entity/SourceType.java new file mode 100644 index 0000000..42a4fc7 --- /dev/null +++ b/backend/src/main/java/_team/onmyway/entity/SourceType.java @@ -0,0 +1,7 @@ +package _team.onmyway.entity; + +public enum SourceType { + KAKAO, + NAVER, + GOOGLE +} \ No newline at end of file diff --git a/backend/src/main/java/_team/onmyway/exception/GlobalExceptionHandler b/backend/src/main/java/_team/onmyway/exception/GlobalExceptionHandler new file mode 100644 index 0000000..fc04527 --- /dev/null +++ b/backend/src/main/java/_team/onmyway/exception/GlobalExceptionHandler @@ -0,0 +1,4 @@ +@ExceptionHandler(Exception.class) +public ResponseEntity handleException(Exception e) { + return ResponseEntity.status(500).body("NETWORK_ERROR"); +} \ No newline at end of file diff --git a/backend/src/main/java/_team/onmyway/repository/CategoryMappingRepository.java b/backend/src/main/java/_team/onmyway/repository/CategoryMappingRepository.java new file mode 100644 index 0000000..cf81f40 --- /dev/null +++ b/backend/src/main/java/_team/onmyway/repository/CategoryMappingRepository.java @@ -0,0 +1,12 @@ +package _team.onmyway.repository; + +import _team.onmyway.entity.CategoryMapping; +import _team.onmyway.entity.SourceType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CategoryMappingRepository extends JpaRepository { + + Optional findBySourceAndApiCategory(SourceType source, String apiCategory); +} \ No newline at end of file diff --git a/backend/src/main/java/_team/onmyway/repository/PlaceRepository.java b/backend/src/main/java/_team/onmyway/repository/PlaceRepository.java new file mode 100644 index 0000000..3a09b28 --- /dev/null +++ b/backend/src/main/java/_team/onmyway/repository/PlaceRepository.java @@ -0,0 +1,28 @@ +package _team.onmyway.repository; + +import _team.onmyway.entity.Place; +import _team.onmyway.entity.ServiceCategory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface PlaceRepository extends JpaRepository { + + @Query(value = """ + SELECT * FROM places p + WHERE p.lat BETWEEN :latMin AND :latMax + AND p.lng BETWEEN :lngMin AND :lngMax + AND p.service_category_id = :#{#category.id} + ORDER BY RANDOM() + LIMIT :limit + """, nativeQuery = true) + List findRandomByCategory( + double latMin, + double latMax, + double lngMin, + double lngMax, + ServiceCategory category, + int limit + ); +} \ No newline at end of file diff --git a/backend/src/main/java/_team/onmyway/repository/ServiceCategoryRepository.java b/backend/src/main/java/_team/onmyway/repository/ServiceCategoryRepository.java new file mode 100644 index 0000000..f9bd373 --- /dev/null +++ b/backend/src/main/java/_team/onmyway/repository/ServiceCategoryRepository.java @@ -0,0 +1,7 @@ +package _team.onmyway.repository; + +import _team.onmyway.entity.ServiceCategory; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ServiceCategoryRepository extends JpaRepository { +} diff --git a/backend/src/main/java/_team/onmyway/service/RecommendationService.java b/backend/src/main/java/_team/onmyway/service/RecommendationService.java new file mode 100644 index 0000000..0361870 --- /dev/null +++ b/backend/src/main/java/_team/onmyway/service/RecommendationService.java @@ -0,0 +1,70 @@ +package _team.onmyway.service; + +import _team.onmyway.dto.RecommendationResponseDTO; +import _team.onmyway.entity.Place; +import _team.onmyway.entity.ServiceCategory; +import _team.onmyway.repository.PlaceRepository; +import _team.onmyway.repository.ServiceCategoryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RecommendationService { + + private final PlaceRepository placeRepository; + private final ServiceCategoryRepository serviceCategoryRepository; + + private static final double RANGE = 0.005; // 약 500m + + public RecommendationResponseDTO recommend(double lat, double lng) { + + double latMin = lat - RANGE; + double latMax = lat + RANGE; + double lngMin = lng - RANGE; + double lngMax = lng + RANGE; + + List result = new ArrayList<>(); + + // 카테고리별 개수 + result.addAll(getPlaces(latMin, latMax, lngMin, lngMax, 1L, 2)); // 한잔 2 + result.addAll(getPlaces(latMin, latMax, lngMin, lngMax, 2L, 2)); // 한입 2 + result.addAll(getPlaces(latMin, latMax, lngMin, lngMax, 3L, 1)); // 한숨 1 + result.addAll(getPlaces(latMin, latMax, lngMin, lngMax, 4L, 1)); // 한손 1 + result.addAll(getPlaces(latMin, latMax, lngMin, lngMax, 5L, 1)); // 한눈 1 + + return RecommendationResponseDTO.from(result, lat, lng); + } + + public RecommendationResponseDTO recommendByCategory(double lat, double lng, Long categoryId) { + + double latMin = lat - RANGE; + double latMax = lat + RANGE; + double lngMin = lng - RANGE; + double lngMax = lng + RANGE; + + ServiceCategory category = serviceCategoryRepository.findById(categoryId) + .orElseThrow(); + + List places = placeRepository.findRandomByCategory( + latMin, latMax, lngMin, lngMax, category, 7 + ); + + return RecommendationResponseDTO.from(places, lat, lng); + } + + private List getPlaces(double latMin, double latMax, + double lngMin, double lngMax, + Long categoryId, int limit) { + + ServiceCategory category = serviceCategoryRepository.findById(categoryId) + .orElseThrow(); + + return placeRepository.findRandomByCategory( + latMin, latMax, lngMin, lngMax, category, limit + ); + } +} \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4e90a87..cfbeec4 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -31,6 +31,10 @@ spring.jpa.hibernate.ddl-auto=update spring.jpa.database=postgresql spring.jpa.properties.hibernate.default_schema=public +# SQL init +spring.sql.init.mode=always +spring.jpa.defer-datasource-initialization=true + # JWT jwt.secret=${JWT_SECRET} jwt.access-expiration=${ACCESS_TOKEN_EXPIRATION} diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql new file mode 100644 index 0000000..e78d8ad --- /dev/null +++ b/backend/src/main/resources/data.sql @@ -0,0 +1,14 @@ +INSERT INTO service_category (id, name) VALUES (1, '한잔'); +INSERT INTO service_category (id, name) VALUES (2, '한입'); +INSERT INTO service_category (id, name) VALUES (3, '한숨'); +INSERT INTO service_category (id, name) VALUES (4, '한손'); +INSERT INTO service_category (id, name) VALUES (5, '한눈'); + +/* +-- 카카오 카테고리 매핑 +INSERT INTO category_mapping (source, api_category, service_category_id) +VALUES ('KAKAO', '카페', 1); + +INSERT INTO category_mapping (source, api_category, service_category_id) +VALUES ('KAKAO', '음식점', 2); + */ \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..aba25f7 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "frontend", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 540cdc2e6a821106ae5c6f94f2b0b99b5d9aa3e1 Mon Sep 17 00:00:00 2001 From: scholar-star Date: Wed, 3 Jun 2026 15:36:29 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EC=97=90=20=EA=B0=80=EA=B2=8C=EB=A5=BC=20=EB=9C=A8=EA=B2=8C=20?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=B9=84?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=EB=A1=9C=20=EC=88=98=EC=A0=95(=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=95=8C=EB=AC=B8=EC=97=90...)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onmyway/controller/RouteController.java | 58 ++++++++----------- .../onmyway/repository/PhotosRepository.java | 4 ++ .../onmyway/repository/PlaceRepository.java | 9 ++- .../_team/onmyway/service/ImageService.java | 6 +- .../service/RecommendationService.java | 17 ++++-- 5 files changed, 49 insertions(+), 45 deletions(-) diff --git a/backend/src/main/java/_team/onmyway/controller/RouteController.java b/backend/src/main/java/_team/onmyway/controller/RouteController.java index ccf5192..94e16c1 100644 --- a/backend/src/main/java/_team/onmyway/controller/RouteController.java +++ b/backend/src/main/java/_team/onmyway/controller/RouteController.java @@ -18,6 +18,7 @@ import reactor.core.publisher.Mono; import java.util.List; +import java.util.function.Function; @RestController @RequestMapping("/route") @@ -28,47 +29,34 @@ public class RouteController { private final ObjectMapper objectMapper; @PostMapping("/findOut") - public ResponseEntity getFindOutRoute(@RequestBody List positions) { - if (positions.isEmpty()) return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - PositionDTO start = positions.get(0); - - RouteResponseDTO routing = routeService.findOutRoute(positions).block(); - Mono recommendations = recommendationService.recommendByRoute(routing, start.getLat(), start.getLon()); - - ObjectNode response = objectMapper.createObjectNode(); - response.set("route", objectMapper.valueToTree(routing)); - response.set("recommendations", objectMapper.valueToTree(recommendations)); - - return new ResponseEntity<>(response, HttpStatus.OK); + public Mono> getFindOutRoute(@RequestBody List positions) { + return processRouteAndRecommend(positions, routeService::findOutRoute); } @PostMapping("/right") - public ResponseEntity getRightRoute(@RequestBody List positions) { - if (positions.isEmpty()) return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - PositionDTO start = positions.get(0); - - RouteResponseDTO routing = routeService.rightRoute(positions).block(); - Mono recommendations = recommendationService.recommendByRoute(routing, start.getLat(), start.getLon()); - - ObjectNode response = objectMapper.createObjectNode(); - response.set("route", objectMapper.valueToTree(routing)); - response.set("recommendations", objectMapper.valueToTree(recommendations)); - - return new ResponseEntity<>(response, HttpStatus.OK); + public Mono> getRightRoute(@RequestBody List positions) { + return processRouteAndRecommend(positions, routeService::rightRoute); } @PostMapping("/slow") - public ResponseEntity getRoute(@RequestBody List positions) { - if (positions.isEmpty()) return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - PositionDTO start = positions.get(0); - - RouteResponseDTO routing = routeService.slowRoute(positions).block(); - Mono recommendations = recommendationService.recommendByRoute(routing, start.getLat(), start.getLon()); - - ObjectNode response = objectMapper.createObjectNode(); - response.set("route", objectMapper.valueToTree(routing)); - response.set("recommendations", objectMapper.valueToTree(recommendations)); + public Mono> getRoute(@RequestBody List positions) { + return processRouteAndRecommend(positions, routeService::slowRoute); + } - return new ResponseEntity<>(response, HttpStatus.OK); + // 비동기 로직 한 번에 묶기 + private Mono> processRouteAndRecommend( + List positions, + Function, Mono> processer) { + if (positions.isEmpty()) return Mono.just(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); + PositionDTO start = positions.get(0); + return processer.apply(positions) + .flatMap(routing -> recommendationService.recommendByRoute(routing, start.getLat(), start.getLon()) + .map(recommendations -> { + ObjectNode response = objectMapper.createObjectNode(); + response.set("route", objectMapper.valueToTree(routing)); + response.set("recommendations", objectMapper.valueToTree(recommendations)); + + return new ResponseEntity<>(response, HttpStatus.OK); + })); } } diff --git a/backend/src/main/java/_team/onmyway/repository/PhotosRepository.java b/backend/src/main/java/_team/onmyway/repository/PhotosRepository.java index 4913434..00eb497 100644 --- a/backend/src/main/java/_team/onmyway/repository/PhotosRepository.java +++ b/backend/src/main/java/_team/onmyway/repository/PhotosRepository.java @@ -6,7 +6,11 @@ import org.springframework.data.jpa.repository.Query; import java.util.List; +import java.util.Optional; public interface PhotosRepository extends JpaRepository { + boolean existsByPlaceId(Long placeId); + + Optional findFirstByPlaceId(Long placeId); public List findByPlace(Place place); } diff --git a/backend/src/main/java/_team/onmyway/repository/PlaceRepository.java b/backend/src/main/java/_team/onmyway/repository/PlaceRepository.java index b684270..25dca26 100644 --- a/backend/src/main/java/_team/onmyway/repository/PlaceRepository.java +++ b/backend/src/main/java/_team/onmyway/repository/PlaceRepository.java @@ -2,6 +2,7 @@ import _team.onmyway.entity.Place; import _team.onmyway.entity.ServiceCategory; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -40,12 +41,14 @@ List findRandomByCategoryInRadius( int limit ); + @EntityGraph(attributePaths = {"workingTimes", "serviceCategory"}) + // JPQL로 해결 @Query(value = """ - SELECT * FROM place p - WHERE p.service_category_id IN :categoryIds + SELECT p FROM Place p + WHERE p.serviceCategory.id IN :categoryIds AND p.lat BETWEEN :minLat AND :maxLat AND p.lng BETWEEN :minLng AND :maxLng - """, nativeQuery = true) + """) List findByBoundingBox( List categoryIds, double minLat, diff --git a/backend/src/main/java/_team/onmyway/service/ImageService.java b/backend/src/main/java/_team/onmyway/service/ImageService.java index 584dee3..96ac69e 100644 --- a/backend/src/main/java/_team/onmyway/service/ImageService.java +++ b/backend/src/main/java/_team/onmyway/service/ImageService.java @@ -33,9 +33,9 @@ public ImageService(PhotosRepository photosRepository) { } public Mono getImageURL(Place p) { - List dbPhotos = p.getPhotos(); - if (dbPhotos.size() > 0) { - return Mono.just(dbPhotos.get(0).getPhotoURL()); + boolean hasPhoto = photosRepository.existsById(p.getId()); + if (hasPhoto) { + return Mono.just(photosRepository.findFirstByPlaceId(p.getId()).get().getPhotoURL()); } else { return webClient.get() .uri(uri -> uri diff --git a/backend/src/main/java/_team/onmyway/service/RecommendationService.java b/backend/src/main/java/_team/onmyway/service/RecommendationService.java index 3419b86..3f81f70 100644 --- a/backend/src/main/java/_team/onmyway/service/RecommendationService.java +++ b/backend/src/main/java/_team/onmyway/service/RecommendationService.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.hibernate.jdbc.Work; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -105,10 +106,18 @@ public Mono recommendByRoute(RouteResponseDTO rou .flatMap(p -> imageService.getImageURL(p) .map(imageURL -> { - WorkingTime workingTime = p.getWorkingTimes().get(day%7); - return toPlaceRecommendationDTO(p, userLat, userLng, - workingTime.isClosed(), workingTime.getOpenTime(), - workingTime.getCloseTime(), imageURL); + List workingTimes = p.getWorkingTimes(); + int index = day&7; + + if (workingTimes != null & !workingTimes.isEmpty() && index < workingTimes.size()) { + WorkingTime workingTime = workingTimes.get(index); + return toPlaceRecommendationDTO(p, userLat, userLng, + workingTime.isClosed(), workingTime.getOpenTime(), + workingTime.getCloseTime(), imageURL); + } else { + return toPlaceRecommendationDTO(p, userLat, userLng, true, + null, null, imageURL); + } }) ) .collectList() From 0b148ac8f1d25eab29eff2d71e97c290d0c253c5 Mon Sep 17 00:00:00 2001 From: scholar-star Date: Thu, 4 Jun 2026 23:07:13 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EC=98=81=EC=97=85=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0=EB=A5=BC=20?= =?UTF-8?q?=EB=8C=80=EB=B9=84=ED=95=9C=20=EB=B6=84=EA=B8=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC(=EC=9E=84=EC=8B=9C=EC=A0=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RecommendationService.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/_team/onmyway/service/RecommendationService.java b/backend/src/main/java/_team/onmyway/service/RecommendationService.java index 3f81f70..7e6cd67 100644 --- a/backend/src/main/java/_team/onmyway/service/RecommendationService.java +++ b/backend/src/main/java/_team/onmyway/service/RecommendationService.java @@ -258,17 +258,29 @@ private Mono recommendSingleCategory(double lat, doub return Flux.fromIterable(places) .flatMap(place -> { List placeWorkingTime = place.getWorkingTimes(); - WorkingTime workingTime = placeWorkingTime.get(day%7); - - // 2. imageService.getImageURL(place)가 Mono을 반환한다고 가정 - return imageService.getImageURL(place) - .map(imageURL -> toPlaceRecommendationDTO( - place, lat, lng, - workingTime.isClosed(), - workingTime.getOpenTime(), - workingTime.getCloseTime(), - imageURL - )); + if (placeWorkingTime.isEmpty()) { + return imageService.getImageURL(place) + .map(imageURL -> toPlaceRecommendationDTO( + place, lat, lng, + true, + null, + null, + imageURL + )); + } + else { + WorkingTime workingTime = placeWorkingTime.get(day%7); + + // 2. imageService.getImageURL(place)가 Mono을 반환한다고 가정 + return imageService.getImageURL(place) + .map(imageURL -> toPlaceRecommendationDTO( + place, lat, lng, + workingTime.isClosed(), + workingTime.getOpenTime(), + workingTime.getCloseTime(), + imageURL + )); + } }) .collectList() // 3. 비동기로 생성된 DTO들을 다시 List로 모음 .map(placeInfos -> {