diff --git a/gusto/.idea/uiDesigner.xml b/gusto/.idea/uiDesigner.xml new file mode 100644 index 000000000..2b63946d5 --- /dev/null +++ b/gusto/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gusto/src/main/java/com/umc/gusto/GustoApplication.java b/gusto/src/main/java/com/umc/gusto/GustoApplication.java index a2c8fd69e..2293b6ac7 100644 --- a/gusto/src/main/java/com/umc/gusto/GustoApplication.java +++ b/gusto/src/main/java/com/umc/gusto/GustoApplication.java @@ -3,8 +3,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @EnableJpaAuditing +@EnableScheduling @SpringBootApplication public class GustoApplication { diff --git a/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java index 798abf885..d1681a7bc 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java @@ -360,13 +360,11 @@ public PagingResponse getAllGroupList(Long groupId, Long groupListId) { // 그룹 리스트에 해당하는 각 상점 정보 조회 List list = groupLists.stream().map(gl -> { - Optional topReviewOptional = reviewRepository.findFirstByStoreOrderByLikedDesc(gl.getStore()); // 가장 좋아요가 많은 review - String reviewImg = topReviewOptional.map(Review::getImg1).orElse(""); return GroupListResponse.builder() .groupListId(gl.getGroupListId()) .storeId(gl.getStore().getStoreId()) .storeName(gl.getStore().getStoreName()) - .storeProfileImg(reviewImg) + .storeProfileImg(gl.getStore().getImg1() != null ? gl.getStore().getImg1() : "") .userProfileImg(gl.getUser().getProfileImage()) .address(gl.getStore().getAddress()) .build(); diff --git a/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PinByMyCategoryResponse.java b/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PinByMyCategoryResponse.java index d49d8808d..2789b3be7 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PinByMyCategoryResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PinByMyCategoryResponse.java @@ -5,6 +5,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; + @Builder @Getter @NoArgsConstructor @@ -14,6 +16,9 @@ public class PinByMyCategoryResponse{ Long storeId; String storeName; String address; - String reviewImg; + List reviewImg3; + String img1; + String img2; + String img3; Integer reviewCnt; } \ No newline at end of file diff --git a/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java index 9dd458aac..b96aed382 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java @@ -9,7 +9,6 @@ import com.umc.gusto.domain.myCategory.model.response.PinByMyCategoryResponse; import com.umc.gusto.domain.myCategory.repository.MyCategoryRepository; import com.umc.gusto.domain.myCategory.repository.PinRepository; -import com.umc.gusto.domain.review.entity.Review; import com.umc.gusto.domain.review.repository.ReviewRepository; import com.umc.gusto.domain.store.entity.Store; import com.umc.gusto.domain.user.entity.User; @@ -20,6 +19,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.annotation.Transactional; import org.springframework.stereotype.Service; @@ -29,6 +29,7 @@ @Service @RequiredArgsConstructor +@EnableScheduling public class MyCategoryServiceImpl implements MyCategoryService { private final MyCategoryRepository myCategoryRepository; @@ -163,16 +164,18 @@ public PagingResponse getAllPinByMyCategory(User user, String nickname, Long myC List result = pinList.stream() // townName을 기준으로 보일 수 있는 store가 포함된 pin만 보이기 .map(pin -> { Store store = pin.getStore(); - Optional topReviewOptional = reviewRepository.findFirstByStoreOrderByLikedDesc(store); // 가장 좋아요가 많은 review - String reviewImg = topReviewOptional.map(Review::getImg1).orElse(""); // 가장 좋아요가 많은 review 이미지(TO DO: 3개 출력으로 변경) - Integer reviewCnt = reviewRepository.countByStoreAndUserNickname(store, finalUser.getNickname()); // 내가 작성한 리뷰의 개수 == 방문 횟수 + // 가장 좋아요가 많은 review + + Integer reviewCnt = reviewRepository.countByStoreAndUserNickname(store, finalUser.getNickname()); // 내가 작성한 리뷰의 개수 == 방문 횟수 return PinByMyCategoryResponse.builder() .pinId(pin.getPinId()) .storeId(store.getStoreId()) .storeName(store.getStoreName()) .address(store.getAddress()) - .reviewImg(reviewImg) + .img1(store.getImg1() != null ? store.getImg1() : "") + .img2(store.getImg2() != null ? store.getImg2() : "") + .img3(store.getImg3() != null ? store.getImg3() : "") .reviewCnt(reviewCnt) .build(); }) @@ -184,6 +187,7 @@ public PagingResponse getAllPinByMyCategory(User user, String nickname, Long myC .build(); } + @Transactional public void createMyCategory(User user, CreateMyCategoryRequest createMyCategory) { // 중복 이름 체크 diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewService.java b/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewService.java index cfd7e8134..e9e293f7c 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewService.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewService.java @@ -3,6 +3,7 @@ import com.umc.gusto.domain.review.model.request.CreateReviewRequest; import com.umc.gusto.domain.review.model.request.UpdateReviewRequest; import com.umc.gusto.domain.review.model.response.ReviewDetailResponse; +import com.umc.gusto.domain.store.entity.Store; import com.umc.gusto.domain.user.entity.User; import org.springframework.web.multipart.MultipartFile; @@ -16,4 +17,5 @@ public interface ReviewService { ReviewDetailResponse getReview(Long reviewId); void likeReview(User user, Long reviewId); void unlikeReview(User user, Long reviewId); + void updateStoreImages(Store store); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java index be947273a..6859c14c9 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java @@ -22,15 +22,17 @@ import com.umc.gusto.global.exception.customException.NotFoundException; import com.umc.gusto.global.exception.customException.PrivateItemException; import com.umc.gusto.global.util.S3Service; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import org.springframework.transaction.annotation.Propagation; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -216,4 +218,15 @@ private void updateImages(List images, Review review){ if(imageUrls.size()>3) review.updateImg4(imageUrls.get(3)); } + @Transactional(propagation = Propagation.REQUIRES_NEW) // 메소드가 호출될 때마다 새로운 트랜잭션이 시작됨을 의미 + public void updateStoreImages(Store store) { + List top4Reviews = reviewRepository.findFirst4ByStoreOrderByLikedDesc(store); + List reviewImages = top4Reviews.stream() + .map(Review::getImg1) + .collect(Collectors.toList()); + + store.updateImages(reviewImages); + storeRepository.save(store); + } + } diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/entity/Store.java b/gusto/src/main/java/com/umc/gusto/domain/store/entity/Store.java index 6cdc5cba9..e1a108c87 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/entity/Store.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/entity/Store.java @@ -7,6 +7,8 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; +import java.util.List; + @Entity @Getter @Builder @@ -58,6 +60,14 @@ public class Store extends BaseTime { @Column(columnDefinition = "VARCHAR(20)") private String contact; + private String img1; + + private String img2; + + private String img3; + + private String img4; + @Builder.Default @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false, length = 10) @@ -66,5 +76,22 @@ public class Store extends BaseTime { public enum StoreStatus { ACTIVE, INACTIVE, CLOSED } + + public void updateImages(List reviewImages) { + // reviewImages 리스트에서 이미지를 가져와 각 필드에 할당 + if (!reviewImages.isEmpty()) { + this.img1 = reviewImages.get(0); + } + if (reviewImages.size() > 1) { + this.img2 = reviewImages.get(1); + } + if (reviewImages.size() > 2) { + this.img3 = reviewImages.get(2); + } + if (reviewImages.size() > 3) { + this.img4 = reviewImages.get(3); + } + } + } diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreDetailResponse.java b/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreDetailResponse.java index b86c9690e..276bfc6a6 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreDetailResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreDetailResponse.java @@ -20,7 +20,10 @@ public class GetStoreDetailResponse{ String storeName; String address; Boolean pin; // 찜 여부 - List reviewImg4; + String img1; + String img2; + String img3; + String img4; PagingResponse reviews; } diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreInfoResponse.java b/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreInfoResponse.java index d7539ba5c..a0982abe6 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreInfoResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreInfoResponse.java @@ -14,5 +14,5 @@ public class GetStoreInfoResponse { String categoryString; String storeName; String address; - String reviewImg; + String img1; } \ No newline at end of file diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreResponse.java b/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreResponse.java index f84760437..e217f367b 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/model/response/GetStoreResponse.java @@ -25,7 +25,9 @@ public class GetStoreResponse{ Double latitude; Map businessDay; String contact; - List reviewImg3; + String img1; + String img2; + String img3; Boolean pin; // 찜 여부 // 하루 영업 시간을 나타내는 내부 클래스 diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java index ee83a0990..ea4239e9a 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java @@ -4,6 +4,7 @@ import com.umc.gusto.domain.myCategory.repository.PinRepository; import com.umc.gusto.domain.review.entity.Review; import com.umc.gusto.domain.review.repository.ReviewRepository; +import com.umc.gusto.domain.review.service.ReviewService; import com.umc.gusto.domain.store.entity.OpeningHours; import com.umc.gusto.domain.store.entity.Store; import com.umc.gusto.domain.store.model.response.*; @@ -15,7 +16,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.PageRequest; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,6 +31,8 @@ public class StoreServiceImpl implements StoreService{ private final ReviewRepository reviewRepository; private final PinRepository pinRepository; private final OpeningHoursRepository openingHoursRepository; + private final ReviewService reviewService; + private static final int PAGE_SIZE_FIRST = 3; private static final int PAGE_SIZE = 6; @@ -50,12 +53,6 @@ public List getStores(User user, List storeIds) { ); businessDays.put(openingHours.getBusinessDay(), timing); } - - List top3Reviews = reviewRepository.findFirst3ByStoreOrderByLikedDesc(store); - - List reviewImg = top3Reviews.stream() - .map(review -> Optional.ofNullable(review.getImg1()).orElse("")) - .collect(Collectors.toList()); boolean isPinned = pinRepository.existsByUserAndStoreStoreId(user, storeId); @@ -67,7 +64,9 @@ public List getStores(User user, List storeIds) { .longitude(store.getLongitude()) .latitude(store.getLatitude()) .businessDay(businessDays) - .reviewImg3(reviewImg) + .img1(store.getImg1() != null ? store.getImg1() : "") + .img2(store.getImg2() != null ? store.getImg2() : "") + .img3(store.getImg3() != null ? store.getImg3() : "") .pin(isPinned) .build()); } @@ -85,11 +84,7 @@ public GetStoreDetailResponse getStoreDetail(User user, Long storeId, LocalDate // .orElseThrow(() -> new GeneralException(Code.CATEGORY_NOT_FOUND)); Long pinId = pinRepository.findByUserAndStoreStoreId(user, storeId); - List top4Reviews = reviewRepository.findFirst4ByStoreOrderByLikedDesc(store); - List reviewImg = top4Reviews.stream() - .map(review -> Optional.ofNullable(review.getImg1()).orElse("")) - .collect(Collectors.toList()); // reviews 페이징 처리 (3,6,6...) int pageSize; @@ -113,10 +108,10 @@ public GetStoreDetailResponse getStoreDetail(User user, Long storeId, LocalDate .nickname(reviewer.getNickname()) .liked(review.getLiked()) .comment(review.getComment()) - .img1(review.getImg1()) - .img2(review.getImg2()) - .img3(review.getImg3()) - .img4(review.getImg4()) + .img1(review.getImg1() != null ? review.getImg1() : "") + .img2(review.getImg2() != null ? review.getImg2() : "") + .img3(review.getImg3() != null ? review.getImg1() : "") + .img4(review.getImg4() != null ? review.getImg1() : "") .build(); }) .toList(); @@ -129,7 +124,10 @@ public GetStoreDetailResponse getStoreDetail(User user, Long storeId, LocalDate .categoryString(store.getCategoryString()) .storeName(store.getStoreName()) .address(store.getAddress()) - .reviewImg4(reviewImg) + .img1(store.getImg1() != null ? store.getImg1() : "") + .img2(store.getImg2() != null ? store.getImg2() : "") + .img3(store.getImg3() != null ? store.getImg3() : "") + .img4(store.getImg4() != null ? store.getImg4() : "") .pin(isPinned) .reviews(PagingResponse.builder() .hasNext(reviews.hasNext()) @@ -183,8 +181,6 @@ public List getPinStoresByCategoryAndLocation(User user, Lo for (Pin pin : pins){ Store store = pin.getStore(); - Optional topReviewOptional = reviewRepository.findFirstByStoreOrderByLikedDesc(store); - String reviewImg = topReviewOptional.map(Review::getImg1).orElse(""); boolean hasVisited = reviewRepository.existsByStoreAndUserNickname(store, user.getNickname()); GetStoreInfoResponse getStoreInfoResponse = GetStoreInfoResponse.builder() @@ -192,7 +188,7 @@ public List getPinStoresByCategoryAndLocation(User user, Lo .categoryString(store.getCategoryString()) .storeName(store.getStoreName()) .address(store.getAddress()) - .reviewImg(reviewImg) + .img1(store.getImg1() != null ? store.getImg1() : "") .build(); if(!hasVisited){ @@ -230,16 +226,25 @@ public List searchStore(String keyword) { return searchResult.stream() .map(result -> { - Optional review = reviewRepository.findFirstByStoreOrderByLikedDesc(result); - String reviewImg = review.map(Review::getImg1).orElse(""); return GetStoreInfoResponse.builder() .storeId(result.getStoreId()) .storeName(result.getStoreName()) .categoryString(result.getCategoryString()) .address(result.getAddress()) - .reviewImg(reviewImg) + .img1(result.getImg1() != null ? result.getImg1() : "") .build(); }) .collect(Collectors.toList()); } + + // + @Scheduled(cron = "0 0 0 1,16 * ?") + public void updateAllStoreImages() { + List stores = storeRepository.findAll(); + + for (Store store : stores) { + reviewService.updateStoreImages(store); + } + } + }