From 2351919a6f66eee79887ae582a378c6c05805f4f Mon Sep 17 00:00:00 2001 From: BOMIN LYU <83059096+rbm0524@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:22:35 +0900 Subject: [PATCH] =?UTF-8?q?14=EC=A1=B0=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=203=ED=9A=8C=EC=B0=A8=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++++-- .../ordertogether/team14_be/auth/JwtUtil.java | 23 +++++--- .../auth/application/dto/KakaoProperties.java | 3 +- .../auth/application/service/AuthService.java | 3 +- .../auth/presentation/KakaoClient.java | 14 ++--- .../application/service/MemberService.java | 7 +++ .../member/persistence/entity/Member.java | 5 ++ .../details/service/OrderDetailService.java | 13 +++-- .../payment/domain/PaymentOrder.java | 54 +++++++++---------- .../repository/JpaPaymentOrderRepository.java | 11 +--- .../SimpleJpaPaymentOrderRepository.java | 9 +--- .../repository/PaymentOrderRepository.java | 9 ---- .../service/PaymentValidationService.java | 39 -------------- .../spot/controller/SpotController.java | 16 +++--- .../converter/AbstractCodedEnumConverter.java | 4 +- .../controllerdto/SpotCreationRequest.java | 11 ++-- .../dto/controllerdto/SpotModifyRequest.java | 7 ++- .../team14_be/spot/enums/ErrorCode.java | 20 +++++++ .../spot/exception/ErrorResponse.java | 5 ++ .../spot/exception/SpotExceptionHandler.java | 26 +++++++++ .../spot/exception/SpotNotFoundException.java | 15 ++++++ .../team14_be/spot/mapper/SpotMapper.java | 9 ++-- .../spot/repository/SimpleSpotRepository.java | 10 ++++ .../spot/repository/SpotRepository.java | 10 +++- .../team14_be/spot/service/SpotService.java | 42 ++++++++++++++- src/main/resources/application.yml | 4 +- 26 files changed, 239 insertions(+), 147 deletions(-) delete mode 100644 src/main/java/com/ordertogether/team14_be/payment/service/PaymentValidationService.java create mode 100644 src/main/java/com/ordertogether/team14_be/spot/enums/ErrorCode.java create mode 100644 src/main/java/com/ordertogether/team14_be/spot/exception/ErrorResponse.java create mode 100644 src/main/java/com/ordertogether/team14_be/spot/exception/SpotExceptionHandler.java create mode 100644 src/main/java/com/ordertogether/team14_be/spot/exception/SpotNotFoundException.java diff --git a/README.md b/README.md index 5b818135..3238536a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ # Team14_BE 14조 백엔드 -✏ 5주차 PR 리뷰받고 싶은 내용 +✏ 6주차 PR 리뷰받고 싶은 내용 --- 저희 조가 만드는 서비스는 모르는 사람과도 함께 배달 주문을 해서 배달팁과 최소 주문 금액에 대한 부담을 덜 수 있게 하는 서비스입니다. -1. Category를 ENUM으로 변경했습니다. DB에 저장될 때는 "양식", "중식"처럼 저장하는게 나을까요? 아니면 ENUM 자체로 "WESTERN STYLE", "CHINESE STYHLE"로 저장하는 것이 나을까요? 카테고리의 값은 "족발, 보쌈"처럼 선택될 것이고 이것을 Converter를 통해 ENUM값으로 변경해서 처리한 후 DB에 저장 시 ENUM값 그대로 저장되는 것이 나은 것인지 궁금합니다. +중간고사가 다가오다보니 진행이 많이 되지 않았습니다... -2. Spot은 카카오 지도 API와 연계됩니다. 지도 상에서 특정 위치를 클릭한 후 "스팟 생성하기"를 누르면 배달받을 주소, 카테고리, 가게 이름 등을 통해 "Spot"을 생성합니다. 다른 사람들은 해당 Spot에 참여할 수 있고 togetherOrderLink를 받아서 배달의 민족 앱에서 "함께주문" 기능을 모르는 사람과도 사용할 수 있습니다. 현재는 Controller Dto, Service Dto를 나눈 상태이며 MapStruct를 통해 매핑하는 것으로 수정했습니다. Controller 계층에서 Service 계층에 dto를 넘길 때 매핑을 한 후에 넘기는 것이 맞는지, 아니면 Controller 계층의 dto를 Service 계층에 넘긴 후 Service 계층에서 매핑하는 것이 맞는지 궁금합니다. +1. 지금 수준에서 Spring Cloud를 통해 로드밸런싱을 수행해보고 싶습니다... 그렇지만 프로젝트 완성도가 떨어질 것 같아서 걱정됩니다. 허술해보이는 부분이 많은 것 같은데 멘토님이 보시기에 어떤지 궁금합니다! -3. 전체적으로 개선될 부분이나 더 나은 방식으로 구현하는 것이 좋은 부분을 말씀해주시면 감사하겠습니다! +2. 앞으로 어떤 기술을 적용해보면 좋을지, 또는 코드를 보시면서 다른 방법을 적용하면 더 좋았을 것 같다고 생각하신 부분이 궁금합니다! + +3. JwtUtil + + 멘토님께서 조언해주신대로 JwtUtil을 스프링빈으로 등록하지 않고 정적 유틸 클래스로 작성함으로써 사용하고자 하였습니다. 다만, application.yaml에서 다루고 있는 변수(expireTime, secretKey)가 있다 보니 JwtUtil을 스프링빈으로 등록하는 것이 필요해졌습니다. + 혹시 JwtUtil을 스프링빈으로 등록하지 않고 변수를 적용할 수 있는 방법을 아시나요?🥺 + + 스프링빈으로 등록을 안 하면 사용시마다 생성자 주입을 통해 사용하게 될 것 같은데 스프링빈 등록과 비교해서 어떤 걸 올바른지 잘 모르겠습니다ㅜㅜ + + 혹시 이에 대한 명쾌한 해답이 있다면 조언해주시면 감사드리겠습니다:) diff --git a/src/main/java/com/ordertogether/team14_be/auth/JwtUtil.java b/src/main/java/com/ordertogether/team14_be/auth/JwtUtil.java index 5793f82f..074119d9 100644 --- a/src/main/java/com/ordertogether/team14_be/auth/JwtUtil.java +++ b/src/main/java/com/ordertogether/team14_be/auth/JwtUtil.java @@ -1,26 +1,33 @@ package com.ordertogether.team14_be.auth; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Date; import javax.crypto.SecretKey; -import org.springframework.stereotype.Component; +import org.springframework.beans.factory.annotation.Value; -@Component public class JwtUtil { - private static final SecretKey key = Keys.secretKeyFor(io.jsonwebtoken.SignatureAlgorithm.HS256); - private static final int EXPIRE_TIME = 1; + private final SecretKey key; - public static String generateToken(String email) { + @Value("${jwt.expire-time}") + private int expireTime; + + public JwtUtil(@Value("${key.jwt.secret-key}") String secretKey) { + this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + public String generateToken(Long data) { Date now = new Date(); - Date exp = new Date(now.getTime() + Duration.ofHours(EXPIRE_TIME).toMillis()); + Date exp = new Date(now.getTime() + Duration.ofHours(expireTime).toMillis()); return Jwts.builder() - .setSubject(email) + .setSubject(data.toString()) .setIssuedAt(now) .setExpiration(exp) - .signWith(key) + .signWith(key, SignatureAlgorithm.HS256) .compact(); } } diff --git a/src/main/java/com/ordertogether/team14_be/auth/application/dto/KakaoProperties.java b/src/main/java/com/ordertogether/team14_be/auth/application/dto/KakaoProperties.java index 97371e53..8a516094 100644 --- a/src/main/java/com/ordertogether/team14_be/auth/application/dto/KakaoProperties.java +++ b/src/main/java/com/ordertogether/team14_be/auth/application/dto/KakaoProperties.java @@ -4,7 +4,8 @@ import org.springframework.util.LinkedMultiValueMap; @ConfigurationProperties(prefix = "kakao") -public record KakaoProperties(String clientId, String redirectUrl) { +public record KakaoProperties( + String clientId, String redirectUrl, String tokenUrl, String userApiUrl) { public LinkedMultiValueMap createBody(String code) { LinkedMultiValueMap body = new LinkedMultiValueMap<>(); diff --git a/src/main/java/com/ordertogether/team14_be/auth/application/service/AuthService.java b/src/main/java/com/ordertogether/team14_be/auth/application/service/AuthService.java index 7ec5e4d1..209b0cf6 100644 --- a/src/main/java/com/ordertogether/team14_be/auth/application/service/AuthService.java +++ b/src/main/java/com/ordertogether/team14_be/auth/application/service/AuthService.java @@ -13,13 +13,14 @@ public class AuthService { private final KakaoClient kakaoClient; private final MemberService memberService; + private static JwtUtil jwtUtil; public String kakaoLogin(String authorizationCode) { String kakaoToken = kakaoClient.getAccessToken(authorizationCode); // 인가코드로부터 카카오토큰 발급 KakaoUserInfo kakaoUserInfo = kakaoClient.getUserInfo((kakaoToken)); String userKakaoEmail = kakaoUserInfo.kakaoAccount().email(); memberService.findOrCreateMember(userKakaoEmail); - String serviceToken = JwtUtil.generateToken(userKakaoEmail); + String serviceToken = jwtUtil.generateToken(memberService.getMemberId(userKakaoEmail)); return serviceToken; } diff --git a/src/main/java/com/ordertogether/team14_be/auth/presentation/KakaoClient.java b/src/main/java/com/ordertogether/team14_be/auth/presentation/KakaoClient.java index 25807453..f6f8ef47 100644 --- a/src/main/java/com/ordertogether/team14_be/auth/presentation/KakaoClient.java +++ b/src/main/java/com/ordertogether/team14_be/auth/presentation/KakaoClient.java @@ -6,8 +6,6 @@ import java.net.URI; import java.util.Objects; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; @@ -18,22 +16,16 @@ @RequiredArgsConstructor public class KakaoClient { - @Autowired private final RestClient restClient; + private final RestClient restClient; private final KakaoProperties kakaoProperties; - @Value("${kakao.auth.token.url}") - private String kakaoTokenUrl; - - @Value("${kakao.user.api.url}") - private String kakaoUserApiUrl; - public String getAccessToken(String authorizationCode) { LinkedMultiValueMap body = kakaoProperties.createBody(authorizationCode); var response = restClient .post() - .uri(URI.create(kakaoTokenUrl)) + .uri(URI.create(kakaoProperties.tokenUrl())) .contentType(MediaType.APPLICATION_FORM_URLENCODED) .body(body) .retrieve() @@ -45,7 +37,7 @@ public String getAccessToken(String authorizationCode) { public KakaoUserInfo getUserInfo(String accessToken) { return restClient .get() - .uri(kakaoUserApiUrl) + .uri(kakaoProperties.userApiUrl()) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .accept(MediaType.APPLICATION_JSON) .retrieve() diff --git a/src/main/java/com/ordertogether/team14_be/member/application/service/MemberService.java b/src/main/java/com/ordertogether/team14_be/member/application/service/MemberService.java index 6d9c51d3..0a3e4e63 100644 --- a/src/main/java/com/ordertogether/team14_be/member/application/service/MemberService.java +++ b/src/main/java/com/ordertogether/team14_be/member/application/service/MemberService.java @@ -2,6 +2,7 @@ import com.ordertogether.team14_be.member.persistence.MemberRepository; import com.ordertogether.team14_be.member.persistence.entity.Member; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -11,6 +12,7 @@ public class MemberService { private final MemberRepository memberRepository; + @Transactional public void findOrCreateMember(String email) { Member member = memberRepository @@ -21,4 +23,9 @@ public void findOrCreateMember(String email) { return memberRepository.saveAndFlush(newMember); }); } + + public Long getMemberId(String email) { + Member member = memberRepository.findByEmail(email).get(); + return member.getId(); + } } diff --git a/src/main/java/com/ordertogether/team14_be/member/persistence/entity/Member.java b/src/main/java/com/ordertogether/team14_be/member/persistence/entity/Member.java index cb06b470..8462ac37 100644 --- a/src/main/java/com/ordertogether/team14_be/member/persistence/entity/Member.java +++ b/src/main/java/com/ordertogether/team14_be/member/persistence/entity/Member.java @@ -71,4 +71,9 @@ public static Member from(String email) { member.setEmail(email); // 이메일 설정 return member; } + + public void modifyMemberInfo(String deliveryName, String phoneNumber) { + this.deliveryName = deliveryName; + this.phoneNumber = phoneNumber; + } } diff --git a/src/main/java/com/ordertogether/team14_be/order/details/service/OrderDetailService.java b/src/main/java/com/ordertogether/team14_be/order/details/service/OrderDetailService.java index e7036955..481733d2 100644 --- a/src/main/java/com/ordertogether/team14_be/order/details/service/OrderDetailService.java +++ b/src/main/java/com/ordertogether/team14_be/order/details/service/OrderDetailService.java @@ -6,7 +6,7 @@ import com.ordertogether.team14_be.order.details.dto.create.CreateOrderDetailResponseDto; import com.ordertogether.team14_be.order.details.entity.OrderDetail; import com.ordertogether.team14_be.order.details.repository.OrderDetailRepository; -import com.ordertogether.team14_be.spot.entity.Spot; +import com.ordertogether.team14_be.spot.dto.servicedto.SpotDto; import com.ordertogether.team14_be.spot.repository.SpotRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -25,19 +25,18 @@ public CreateOrderDetailResponseDto createOrderDetail( // 참여자 본인 정보 설정 Member member = - memberRepository.findById(createOrderDetailRequestDto.getParticipantId()) + memberRepository + .findById(createOrderDetailRequestDto.getParticipantId()) .orElseThrow(() -> new IllegalArgumentException("참여자 정보가 없습니다.")); // 스팟 정보 설정 - Spot spot = - spotRepository - .findById(createOrderDetailRequestDto.getSpotId()) - .orElseThrow(() -> new IllegalArgumentException("스팟 정보를 찾을 수 없습니다.")); + SpotDto spot = + spotRepository.findByIdAndIsDeletedFalse(createOrderDetailRequestDto.getSpotId()); OrderDetail orderDetail = OrderDetail.builder() .member(member) - .spot(spot) + // .spot(spot) .price(createOrderDetailRequestDto.getPrice()) .isPayed(createOrderDetailRequestDto.isPayed()) .build(); diff --git a/src/main/java/com/ordertogether/team14_be/payment/domain/PaymentOrder.java b/src/main/java/com/ordertogether/team14_be/payment/domain/PaymentOrder.java index 8b2d9aec..599d7061 100644 --- a/src/main/java/com/ordertogether/team14_be/payment/domain/PaymentOrder.java +++ b/src/main/java/com/ordertogether/team14_be/payment/domain/PaymentOrder.java @@ -1,48 +1,42 @@ package com.ordertogether.team14_be.payment.domain; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.ManyToOne; import java.math.BigDecimal; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; +import java.util.Objects; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.ToString; -import lombok.experimental.SuperBuilder; -@Entity +@Builder @Getter -@SuperBuilder @ToString -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class PaymentOrder extends BaseEntity { +public class PaymentOrder { - @Column(nullable = false) - private Long sellerId; // 판매자 식별자 + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - private PaymentEvent paymentEvent; + private Long productId; - @ManyToOne(fetch = FetchType.LAZY) - private Product productId; + private String orderId; - @Column(nullable = false) - private String orderId; + private String orderName; - @Column(precision = 10, scale = 2) - private BigDecimal amount; // 결제 금액 + private BigDecimal amount; - @Enumerated(EnumType.STRING) - @Builder.Default - private PaymentOrderStatus paymentOrderStatus = PaymentOrderStatus.READY; + public PaymentOrder updateProductInfo(Product product) { + if (isProductMismatch(product)) { + throw new IllegalArgumentException("상품 정보가 일치하지 않습니다."); + } - @Builder.Default private Byte retryCount = 0; // 재시도 횟수 + this.orderName = product.getName(); + this.amount = product.getPrice(); - @Builder.Default private Byte retryThreshold = 5; // 재시도 허용 임계값 + return this; + } + + public boolean isMissingProductInfo() { + return Objects.isNull(orderName) && Objects.isNull(amount); + } + + private boolean isProductMismatch(Product product) { + return !Objects.equals(productId, product.getId()); + } } diff --git a/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/JpaPaymentOrderRepository.java b/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/JpaPaymentOrderRepository.java index 9a3cbaf7..1eade54e 100644 --- a/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/JpaPaymentOrderRepository.java +++ b/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/JpaPaymentOrderRepository.java @@ -6,9 +6,7 @@ import com.ordertogether.team14_be.payment.persistence.jpa.mapper.PaymentOrderMapper; import com.ordertogether.team14_be.payment.persistence.jpa.mapper.ProductMapper; import com.ordertogether.team14_be.payment.persistence.repository.PaymentOrderRepository; -import java.math.BigDecimal; import java.util.List; -import java.util.NoSuchElementException; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -55,19 +53,12 @@ public Optional findById(Long id) { return simpleJpaPaymentOrderRepository.findById(id).map(PaymentOrderMapper::mapToDomain); } - @Override - public BigDecimal getPaymentTotalAmount(String orderId) { - return simpleJpaPaymentOrderRepository - .getPaymentTotalAmount(orderId) - .orElseThrow(() -> new NoSuchElementException("주문 번호: %s 에 해당하는 주문이 존재하지 않습니다.")); - } - private ProductEntity getProductEntity(PaymentOrder paymentOrder) { return simpleJpaProductRepository .findById(paymentOrder.getProductId()) .orElseThrow( () -> - new NoSuchElementException( + new IllegalArgumentException( String.format("상품 아이디 %s에 해당하는 상품이 없습니다.", paymentOrder.getProductId()))); } } diff --git a/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/SimpleJpaPaymentOrderRepository.java b/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/SimpleJpaPaymentOrderRepository.java index 11709f8a..f5c5d38e 100644 --- a/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/SimpleJpaPaymentOrderRepository.java +++ b/src/main/java/com/ordertogether/team14_be/payment/persistence/jpa/repository/SimpleJpaPaymentOrderRepository.java @@ -1,15 +1,8 @@ package com.ordertogether.team14_be.payment.persistence.jpa.repository; import com.ordertogether.team14_be.payment.persistence.jpa.entity.PaymentOrderEntity; -import java.math.BigDecimal; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository -public interface SimpleJpaPaymentOrderRepository extends JpaRepository { - - @Query("SELECT SUM(po.amount) FROM PaymentOrderEntity po WHERE po.orderId = :orderId") - Optional getPaymentTotalAmount(String orderId); -} +public interface SimpleJpaPaymentOrderRepository extends JpaRepository {} diff --git a/src/main/java/com/ordertogether/team14_be/payment/persistence/repository/PaymentOrderRepository.java b/src/main/java/com/ordertogether/team14_be/payment/persistence/repository/PaymentOrderRepository.java index e78b0693..7fa05b8b 100644 --- a/src/main/java/com/ordertogether/team14_be/payment/persistence/repository/PaymentOrderRepository.java +++ b/src/main/java/com/ordertogether/team14_be/payment/persistence/repository/PaymentOrderRepository.java @@ -1,7 +1,6 @@ package com.ordertogether.team14_be.payment.persistence.repository; import com.ordertogether.team14_be.payment.domain.PaymentOrder; -import java.math.BigDecimal; import java.util.List; import java.util.Optional; @@ -12,12 +11,4 @@ public interface PaymentOrderRepository { List saveAll(List paymentOrders); Optional findById(Long id); - - /** - * 주문 번호에 해당하는 주문에 대하여 총 결제 금액을 반환한다. - * - * @param orderId 주문번호 - * @return 총 결제 금액 - */ - BigDecimal getPaymentTotalAmount(String orderId); } diff --git a/src/main/java/com/ordertogether/team14_be/payment/service/PaymentValidationService.java b/src/main/java/com/ordertogether/team14_be/payment/service/PaymentValidationService.java deleted file mode 100644 index ac7d5fc5..00000000 --- a/src/main/java/com/ordertogether/team14_be/payment/service/PaymentValidationService.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.ordertogether.team14_be.payment.service; - -import com.ordertogether.team14_be.payment.persistence.repository.PaymentOrderRepository; -import java.math.BigDecimal; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -/** 결제 유효성 검사 */ -public class PaymentValidationService { - - private final PaymentOrderRepository paymentOrderRepository; - - /** - * 요청된 결제 금액과 실제 결제 금액이 일치하는 지 검증합니다. - * - * @param orderId 주문 번호 - * @param requestedAmount 요청한 결제 금액 - * @return 결제 금액이 일치하면 true, 그렇지 않으면 {@link IllegalArgumentException} 발생 - */ - public boolean validate(String orderId, BigDecimal requestedAmount) { - BigDecimal expectedAmount = paymentOrderRepository.getPaymentTotalAmount(orderId); - - if (isAmountMismatch(requestedAmount, expectedAmount)) { - throw new IllegalArgumentException( - "주문 번호: %s 의 결제 요청 금액 %s 원은 예상 결제 금액 %s 원과 다릅니다." - .formatted(orderId, requestedAmount, expectedAmount)); - } - - return true; - } - - private boolean isAmountMismatch(BigDecimal requestedAmount, BigDecimal expectedAmount) { - return requestedAmount.compareTo(expectedAmount) != 0; - } -} diff --git a/src/main/java/com/ordertogether/team14_be/spot/controller/SpotController.java b/src/main/java/com/ordertogether/team14_be/spot/controller/SpotController.java index 491b13b0..a7cc2cc6 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/controller/SpotController.java +++ b/src/main/java/com/ordertogether/team14_be/spot/controller/SpotController.java @@ -1,9 +1,6 @@ package com.ordertogether.team14_be.spot.controller; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotCreationRequest; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotCreationResponse; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotDetailResponse; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotViewedResponse; +import com.ordertogether.team14_be.spot.dto.controllerdto.*; import com.ordertogether.team14_be.spot.mapper.SpotMapper; import com.ordertogether.team14_be.spot.service.SpotService; import java.math.BigDecimal; @@ -41,13 +38,20 @@ public ResponseEntity getSpotDetail(@PathVariable Long id) { return ResponseEntity.ok(spotService.getSpot(id)); } + // 반경 n미터 내 Spot 조회하기 + @GetMapping("/api/v1/spot/{lat}/{lng}/{radius}") // 현재 위치의 좌표와 반지름을 받아옴 + public ResponseEntity> getSpotByRadius( + @PathVariable BigDecimal lat, @PathVariable BigDecimal lng, @PathVariable int radius) { + return ResponseEntity.ok(spotService.getSpotByRadius(lat, lng, radius)); + } + // Spot 수정하기 @PutMapping("/api/v1/spot") public ResponseEntity updateSpot( - @RequestBody SpotCreationRequest spotCreationRequest) { + @RequestBody SpotModifyRequest spotModifyRequest) { return ResponseEntity.ok( SpotMapper.INSTANCE.toSpotCreationResponse( - spotService.updateSpot(SpotMapper.INSTANCE.toSpotDto(spotCreationRequest)))); + spotService.updateSpot(SpotMapper.INSTANCE.toSpotDto(spotModifyRequest)))); } // Spot 삭제하기 diff --git a/src/main/java/com/ordertogether/team14_be/spot/converter/AbstractCodedEnumConverter.java b/src/main/java/com/ordertogether/team14_be/spot/converter/AbstractCodedEnumConverter.java index 6bad1307..3117cbab 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/converter/AbstractCodedEnumConverter.java +++ b/src/main/java/com/ordertogether/team14_be/spot/converter/AbstractCodedEnumConverter.java @@ -15,11 +15,13 @@ public AbstractCodedEnumConverter(Class clazz) { this.clazz = clazz; } + @SuppressWarnings("unchecked") // 경고 억제 @Override public E convertToDatabaseColumn( T attribute) { // Converts the value stored in the entity attribute into the data // representation to be stored in the database. - return attribute.getCode(); + // return attribute.getCode(); -> 코드 저장 ex) KOREAN_STEW -> "찜, 탕, 찌개" + return (E) attribute.name(); // Enum의 이름을 그대로 반환 -> DB에 ENUM을 그대로 저장 } @Override diff --git a/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotCreationRequest.java b/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotCreationRequest.java index 0401380b..d6e62e45 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotCreationRequest.java +++ b/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotCreationRequest.java @@ -1,12 +1,15 @@ package com.ordertogether.team14_be.spot.dto.controllerdto; +import com.ordertogether.team14_be.spot.enums.Category; +import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; public record SpotCreationRequest( Long id, BigDecimal lat, BigDecimal lng, - String storeName, - Integer minimumOrderAmount, - String togetherOrderLink, - String pickUpLocation) {} + @NotNull(message = "가게 이름을 입력해주세요") String storeName, + @NotNull(message = "카테고리를 선택해주세요") Category category, + @NotNull(message = "최소 주문 금액을 입력해주세요") Integer minimumOrderAmount, + @NotNull(message = "배달의 민족 함께 주문링크를 입력해주세요") String togetherOrderLink, + @NotNull(message = "픽업 장소를 입력해주세요") String pickUpLocation) {} diff --git a/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotModifyRequest.java b/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotModifyRequest.java index 3d46ab2f..3e5e6675 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotModifyRequest.java +++ b/src/main/java/com/ordertogether/team14_be/spot/dto/controllerdto/SpotModifyRequest.java @@ -1,4 +1,9 @@ package com.ordertogether.team14_be.spot.dto.controllerdto; +import jakarta.validation.constraints.NotNull; + public record SpotModifyRequest( - Long id, String storeName, Integer minimumOrderAmount, String pickUpLocation) {} + Long id, + @NotNull(message = "가게 이름은 비어있을 수 없습니다.") String storeName, + @NotNull(message = "최소 주문 금액은 비어있을 수 없습니다.") Integer minimumOrderAmount, + @NotNull(message = "픽업 장소는은 비어있을 수 없습니다.") String pickUpLocation) {} diff --git a/src/main/java/com/ordertogether/team14_be/spot/enums/ErrorCode.java b/src/main/java/com/ordertogether/team14_be/spot/enums/ErrorCode.java new file mode 100644 index 00000000..a04e708c --- /dev/null +++ b/src/main/java/com/ordertogether/team14_be/spot/enums/ErrorCode.java @@ -0,0 +1,20 @@ +package com.ordertogether.team14_be.spot.enums; + +import lombok.Getter; + +@Getter +public enum ErrorCode { + INVALID_REQUEST("400", "Invalid request"), + NOT_FOUND("404", "Not found"), + INTERNAL_ERROR("500", "Internal server error"), + SPOT_NOT_FOUND("404", "Spot not found"), + NULL_VALUE_NOT_ALLOWED("400", "Null value not allowed"); + + private final String code; + private final String message; + + ErrorCode(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/com/ordertogether/team14_be/spot/exception/ErrorResponse.java b/src/main/java/com/ordertogether/team14_be/spot/exception/ErrorResponse.java new file mode 100644 index 00000000..32444ec8 --- /dev/null +++ b/src/main/java/com/ordertogether/team14_be/spot/exception/ErrorResponse.java @@ -0,0 +1,5 @@ +package com.ordertogether.team14_be.spot.exception; + +import com.ordertogether.team14_be.spot.enums.ErrorCode; + +public record ErrorResponse(String message, ErrorCode code) {} diff --git a/src/main/java/com/ordertogether/team14_be/spot/exception/SpotExceptionHandler.java b/src/main/java/com/ordertogether/team14_be/spot/exception/SpotExceptionHandler.java new file mode 100644 index 00000000..c987e601 --- /dev/null +++ b/src/main/java/com/ordertogether/team14_be/spot/exception/SpotExceptionHandler.java @@ -0,0 +1,26 @@ +package com.ordertogether.team14_be.spot.exception; + +import com.ordertogether.team14_be.spot.controller.SpotController; +import com.ordertogether.team14_be.spot.enums.ErrorCode; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice(assignableTypes = SpotController.class) +public class SpotExceptionHandler { + + @ExceptionHandler(SpotNotFoundException.class) + public ResponseEntity handleSpotNotFoundException(SpotNotFoundException ex) { + return ResponseEntity.badRequest() + .body(new ErrorResponse(ex.getMessage(), ErrorCode.SPOT_NOT_FOUND)); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException( + ConstraintViolationException ex) { + return ResponseEntity.badRequest() + .body(new ErrorResponse(ex.getMessage(), ErrorCode.NULL_VALUE_NOT_ALLOWED)); + } +} diff --git a/src/main/java/com/ordertogether/team14_be/spot/exception/SpotNotFoundException.java b/src/main/java/com/ordertogether/team14_be/spot/exception/SpotNotFoundException.java new file mode 100644 index 00000000..d50cdfd3 --- /dev/null +++ b/src/main/java/com/ordertogether/team14_be/spot/exception/SpotNotFoundException.java @@ -0,0 +1,15 @@ +package com.ordertogether.team14_be.spot.exception; + +import com.ordertogether.team14_be.spot.enums.ErrorCode; +import lombok.Getter; + +@Getter +public class SpotNotFoundException extends RuntimeException { + + private final ErrorCode errorCode; + + public SpotNotFoundException(String message) { + super(message); + this.errorCode = ErrorCode.SPOT_NOT_FOUND; + } +} diff --git a/src/main/java/com/ordertogether/team14_be/spot/mapper/SpotMapper.java b/src/main/java/com/ordertogether/team14_be/spot/mapper/SpotMapper.java index 566a36cc..283272c0 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/mapper/SpotMapper.java +++ b/src/main/java/com/ordertogether/team14_be/spot/mapper/SpotMapper.java @@ -1,9 +1,6 @@ package com.ordertogether.team14_be.spot.mapper; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotCreationRequest; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotCreationResponse; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotDetailResponse; -import com.ordertogether.team14_be.spot.dto.controllerdto.SpotViewedResponse; +import com.ordertogether.team14_be.spot.dto.controllerdto.*; import com.ordertogether.team14_be.spot.dto.servicedto.SpotDto; import com.ordertogether.team14_be.spot.entity.Spot; import org.mapstruct.Mapper; @@ -30,4 +27,8 @@ public interface SpotMapper { SpotDetailResponse toSpotDetailResponse(SpotDto spotDto); SpotViewedResponse toSpotViewedResponse(SpotDto spotDto); + + SpotModifyRequest toSpotModifyRequest(SpotDto spotDto); + + SpotDto toSpotDto(SpotModifyRequest spotModifyRequest); } diff --git a/src/main/java/com/ordertogether/team14_be/spot/repository/SimpleSpotRepository.java b/src/main/java/com/ordertogether/team14_be/spot/repository/SimpleSpotRepository.java index 37052ef3..416031a2 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/repository/SimpleSpotRepository.java +++ b/src/main/java/com/ordertogether/team14_be/spot/repository/SimpleSpotRepository.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository @@ -12,4 +14,12 @@ public interface SimpleSpotRepository extends JpaRepository { List findByLatAndLngAndIsDeletedFalse(BigDecimal lat, BigDecimal lng); Optional findByIdAndIsDeletedFalse(Long id); + + @Query( + "SELECT sp FROM Spot sp WHERE sp.lat <= :maxlat and sp.lat >= :minlat and sp.lng <= :maxlng and sp.lng >= :minlng and sp.isDeleted = false") + List findAroundSpotAndIsDeletedFalse( + @Param("maxlat") BigDecimal maxlat, + @Param("maxlng") BigDecimal maxlng, + @Param("minlat") BigDecimal minlat, + @Param("minlng") BigDecimal minlng); } diff --git a/src/main/java/com/ordertogether/team14_be/spot/repository/SpotRepository.java b/src/main/java/com/ordertogether/team14_be/spot/repository/SpotRepository.java index 853fea4e..4eed28c5 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/repository/SpotRepository.java +++ b/src/main/java/com/ordertogether/team14_be/spot/repository/SpotRepository.java @@ -2,6 +2,7 @@ import com.ordertogether.team14_be.spot.dto.servicedto.SpotDto; import com.ordertogether.team14_be.spot.entity.Spot; +import com.ordertogether.team14_be.spot.exception.SpotNotFoundException; import com.ordertogether.team14_be.spot.mapper.SpotMapper; import jakarta.persistence.EntityNotFoundException; import java.math.BigDecimal; @@ -23,7 +24,7 @@ public SpotDto findByIdAndIsDeletedFalse(Long id) { return SpotMapper.INSTANCE.toDto( simpleSpotRepository .findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new EntityNotFoundException(id + "에 해당하는 Spot을 찾을 수 없습니다."))); + .orElseThrow(() -> new SpotNotFoundException(id + "에 해당하는 Spot을 찾을 수 없습니다."))); } public List findByLatAndLngAndIsDeletedFalse(BigDecimal lat, BigDecimal lng) { @@ -39,4 +40,11 @@ public void delete(Long id) { .orElseThrow(() -> new EntityNotFoundException(id + "에 해당하는 Spot을 찾을 수 없습니다.")); spot.delete(); } + + public List findAroundSpotAndIsDeletedFalse( + BigDecimal maxX, BigDecimal maxY, BigDecimal minX, BigDecimal minY) { + List spots = simpleSpotRepository.findAroundSpotAndIsDeletedFalse(maxX, maxY, minX, minY); + + return spots.stream().map(SpotMapper.INSTANCE::toDto).toList(); + } } diff --git a/src/main/java/com/ordertogether/team14_be/spot/service/SpotService.java b/src/main/java/com/ordertogether/team14_be/spot/service/SpotService.java index b7ee8dfd..0ec524d5 100644 --- a/src/main/java/com/ordertogether/team14_be/spot/service/SpotService.java +++ b/src/main/java/com/ordertogether/team14_be/spot/service/SpotService.java @@ -1,5 +1,7 @@ package com.ordertogether.team14_be.spot.service; +import static java.lang.Math.abs; + import com.ordertogether.team14_be.spot.dto.controllerdto.SpotCreationResponse; import com.ordertogether.team14_be.spot.dto.controllerdto.SpotDetailResponse; import com.ordertogether.team14_be.spot.dto.controllerdto.SpotViewedResponse; @@ -16,7 +18,7 @@ @Service @RequiredArgsConstructor public class SpotService { - + public static final int EARTH_RADIUS = 6371000; // 6371km private final SpotRepository spotRepository; // Spot 전체 조회하기 @@ -40,6 +42,44 @@ public SpotDetailResponse getSpot(Long id) { return SpotMapper.INSTANCE.toSpotDetailResponse(spotDto); } + // 반경 n미터 내 Spot 조회하기 + @Transactional(readOnly = true) + public List getSpotByRadius(BigDecimal lat, BigDecimal lng, int radius) { + // m당 y 좌표 이동 값 + double mForLatitude = (1 / (EARTH_RADIUS * 1 * (Math.PI / 180))) / 1000; + // m당 x 좌표 이동 값 + double mForLongitude = + (1 / (EARTH_RADIUS * 1 * (Math.PI / 180) * Math.cos(Math.toRadians(lat.doubleValue())))) + / 1000; + + // 현재 위치 기준 검색 거리 좌표 + double maxY = lat.doubleValue() + (radius * mForLatitude); + double minY = lat.doubleValue() - (radius * mForLatitude); + double maxX = lng.doubleValue() + (radius * mForLongitude); + double minX = lng.doubleValue() - (radius * mForLongitude); + + // 원의 지름에 해당하는 정사각형 내에 있는 Spot들을 모두 가져옴 + List resultAroundSpot = + spotRepository.findAroundSpotAndIsDeletedFalse( + BigDecimal.valueOf(maxX), + BigDecimal.valueOf(maxY), + BigDecimal.valueOf(minX), + BigDecimal.valueOf(minY)); + + // 자기 위치에서부터 반경 내에 있는 Spot만 반환 + return resultAroundSpot.stream() + .filter( + spotDto -> { + double distance = + Math.sqrt( + Math.pow(abs(spotDto.getLat().doubleValue() - lat.doubleValue()), 2) + + Math.pow(abs(spotDto.getLng().doubleValue() - lng.doubleValue()), 2)); + return distance <= radius; + }) + .map(SpotMapper.INSTANCE::toSpotViewedResponse) + .toList(); + } + @Transactional public SpotDto updateSpot(SpotDto spotDto) { Spot spot = diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6b783ffd..89d29f2a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,6 @@ spring: datasource: driver-class-name: ${DRIVER_CLASS_NAME} username: ${USERNAME} - password: url: ${URL} jpa: @@ -44,3 +43,6 @@ kakao: key: jwt: secret-key: ${JWT_SECRET_KEY} + +jwt: + expire-time: 1