From 013cdea567fdce4587ab96f066d9e56c63cb345b Mon Sep 17 00:00:00 2001 From: yunjunghun0116 Date: Fri, 2 Aug 2024 15:39:42 +0900 Subject: [PATCH] =?UTF-8?q?add:=20Member=EC=9D=98=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EC=97=90=20point=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Member 필드에 point 추가 --- .../java/gift/controller/PointController.java | 38 ++++++++++++++++++ .../gift/controller/api/GiftOrderApi.java | 1 + .../gift/dto/giftorder/GiftOrderRequest.java | 5 ++- .../java/gift/dto/point/PointRequest.java | 9 +++++ .../java/gift/dto/point/PointResponse.java | 9 +++++ .../gift/exception/GiftOrderException.java | 7 ++++ .../exception/GlobalExceptionHandler.java | 5 +++ src/main/java/gift/model/Member.java | 19 +++++++++ .../java/gift/service/GiftOrderService.java | 5 ++- src/main/java/gift/service/PointService.java | 39 +++++++++++++++++++ src/main/resources/data.sql | 8 ++-- .../java/gift/service/OptionServiceTest.java | 4 +- 12 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 src/main/java/gift/controller/PointController.java create mode 100644 src/main/java/gift/dto/point/PointRequest.java create mode 100644 src/main/java/gift/dto/point/PointResponse.java create mode 100644 src/main/java/gift/exception/GiftOrderException.java create mode 100644 src/main/java/gift/service/PointService.java diff --git a/src/main/java/gift/controller/PointController.java b/src/main/java/gift/controller/PointController.java new file mode 100644 index 000000000..d373c378e --- /dev/null +++ b/src/main/java/gift/controller/PointController.java @@ -0,0 +1,38 @@ +package gift.controller; + +import gift.dto.point.PointRequest; +import gift.dto.point.PointResponse; +import gift.service.PointService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; + +@RestController +@RequestMapping("/api/points") +public class PointController { + + private final PointService pointService; + + public PointController(PointService pointService) { + this.pointService = pointService; + } + + @PostMapping + public ResponseEntity addPoint(@RequestAttribute("memberId") Long memberId, @Valid @RequestBody PointRequest pointRequest) { + var point = pointService.addPoint(memberId, pointRequest.point()); + return ResponseEntity.created(URI.create("/api/points")).body(point); + } + + @GetMapping + public ResponseEntity getPoint(@RequestAttribute("memberId") Long memberId) { + var point = pointService.getPoint(memberId); + return ResponseEntity.ok(point); + } +} diff --git a/src/main/java/gift/controller/api/GiftOrderApi.java b/src/main/java/gift/controller/api/GiftOrderApi.java index f573adc48..2e3eb0d9f 100644 --- a/src/main/java/gift/controller/api/GiftOrderApi.java +++ b/src/main/java/gift/controller/api/GiftOrderApi.java @@ -18,6 +18,7 @@ public interface GiftOrderApi { @Operation(summary = "회원의 새 주문을 생성한다.") @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "주문 생성 성공", content = @Content(schema = @Schema(implementation = GiftOrderResponse.class))), + @ApiResponse(responseCode = "400", description = "주문 생성 실패(사유 : 사용할 수 있는 포인트보다 더 많은 포인트가 입력되었거나 주문 정보가 잘못되었습니다.)", content = @Content(schema = @Schema(hidden = true))), @ApiResponse(responseCode = "401", description = "주문 생성 실패(사유 : 카카오 토큰이 만료되었거나, 허용되지 않은 요청입니다.)", content = @Content(schema = @Schema(hidden = true))), @ApiResponse(responseCode = "500", description = "내부 서버의 오류", content = @Content(schema = @Schema(hidden = true))) }) diff --git a/src/main/java/gift/dto/giftorder/GiftOrderRequest.java b/src/main/java/gift/dto/giftorder/GiftOrderRequest.java index fc81a824e..5055e1884 100644 --- a/src/main/java/gift/dto/giftorder/GiftOrderRequest.java +++ b/src/main/java/gift/dto/giftorder/GiftOrderRequest.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; public record GiftOrderRequest( @NotNull(message = "상품 옵션은 반드시 선택되어야 합니다.") @@ -12,6 +13,8 @@ public record GiftOrderRequest( @Max(value = 100_000_000, message = "수량은 최소 1개 이상, 1억개 미만입니다.") Integer quantity, @NotBlank(message = "메시지의 길이는 최소 1자 이상이어야 합니다.") - String message + String message, + @PositiveOrZero(message = "포인트는 0보다 크거나 같아야 합니다.") + Integer point ) { } diff --git a/src/main/java/gift/dto/point/PointRequest.java b/src/main/java/gift/dto/point/PointRequest.java new file mode 100644 index 000000000..bef49089c --- /dev/null +++ b/src/main/java/gift/dto/point/PointRequest.java @@ -0,0 +1,9 @@ +package gift.dto.point; + +import jakarta.validation.constraints.Positive; + +public record PointRequest( + @Positive(message = "포인트는 최소 1원 이상이어야 추가할 수 있습니다.") + Integer point +) { +} diff --git a/src/main/java/gift/dto/point/PointResponse.java b/src/main/java/gift/dto/point/PointResponse.java new file mode 100644 index 000000000..47da26563 --- /dev/null +++ b/src/main/java/gift/dto/point/PointResponse.java @@ -0,0 +1,9 @@ +package gift.dto.point; + +public record PointResponse( + Integer point +) { + public static PointResponse of(Integer point) { + return new PointResponse(point); + } +} diff --git a/src/main/java/gift/exception/GiftOrderException.java b/src/main/java/gift/exception/GiftOrderException.java new file mode 100644 index 000000000..ec1089f46 --- /dev/null +++ b/src/main/java/gift/exception/GiftOrderException.java @@ -0,0 +1,7 @@ +package gift.exception; + +public class GiftOrderException extends RuntimeException { + public GiftOrderException(String message) { + super(message); + } +} diff --git a/src/main/java/gift/exception/GlobalExceptionHandler.java b/src/main/java/gift/exception/GlobalExceptionHandler.java index d6465e0c9..115c894d4 100644 --- a/src/main/java/gift/exception/GlobalExceptionHandler.java +++ b/src/main/java/gift/exception/GlobalExceptionHandler.java @@ -46,6 +46,11 @@ public ResponseEntity invalidLoginInfoExceptionHandling() { return getExceptionResponse(INVALID_LOGIN_INFO_MESSAGE, HttpStatus.UNAUTHORIZED); } + @ExceptionHandler(value = GiftOrderException.class) + public ResponseEntity giftOrderExceptionHandling(GiftOrderException exception) { + return getExceptionResponse(exception.getMessage(), HttpStatus.BAD_REQUEST); + } + @ExceptionHandler(value = UnauthorizedAccessException.class) public ResponseEntity unauthorizedAccessExceptionHandling(UnauthorizedAccessException exception) { return getExceptionResponse(exception.getMessage(), HttpStatus.UNAUTHORIZED); diff --git a/src/main/java/gift/model/Member.java b/src/main/java/gift/model/Member.java index 3a168ea81..4db52962f 100644 --- a/src/main/java/gift/model/Member.java +++ b/src/main/java/gift/model/Member.java @@ -1,5 +1,6 @@ package gift.model; +import gift.exception.GiftOrderException; import gift.exception.InvalidLoginInfoException; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -20,6 +21,9 @@ public class Member extends BaseEntity { @Column(name = "password") private String password; @NotNull + @Column(name = "point") + private Integer point = 0; + @NotNull @Column(name = "deleted") private Boolean deleted = Boolean.FALSE; @@ -40,9 +44,24 @@ public String getPassword() { return password; } + public Integer getPoint() { + return point; + } + public void passwordCheck(String inputPassword) { if (!password.equals(inputPassword)) { throw new InvalidLoginInfoException("로그인 정보가 유효하지 않습니다."); } } + + public void addPoint(Integer newPoint) { + this.point = point + newPoint; + } + + public void subtractPoint(Integer usedPoint) { + if (point < usedPoint) { + throw new GiftOrderException("사용가능한 포인트보다 더 많은 포인트를 사용할 수 없습니다."); + } + this.point = point - usedPoint; + } } diff --git a/src/main/java/gift/service/GiftOrderService.java b/src/main/java/gift/service/GiftOrderService.java index ea2055721..6136a5edf 100644 --- a/src/main/java/gift/service/GiftOrderService.java +++ b/src/main/java/gift/service/GiftOrderService.java @@ -21,14 +21,17 @@ public class GiftOrderService { private final GiftOrderRepository giftOrderRepository; private final MemberRepository memberRepository; private final WishProductService wishProductService; + private final PointService pointService; - public GiftOrderService(GiftOrderRepository giftOrderRepository, MemberRepository memberRepository, WishProductService wishProductService) { + public GiftOrderService(GiftOrderRepository giftOrderRepository, MemberRepository memberRepository, WishProductService wishProductService, PointService pointService) { this.giftOrderRepository = giftOrderRepository; this.memberRepository = memberRepository; this.wishProductService = wishProductService; + this.pointService = pointService; } public GiftOrderResponse addGiftOrder(Long memberId, Option option, GiftOrderRequest giftOrderRequest) { + pointService.subtractPoint(memberId, giftOrderRequest.point()); var order = saveGiftOrderWithGiftOrderRequest(memberId, option, giftOrderRequest); wishProductService.deleteAllByMemberIdAndProductId(memberId, option.getProduct().getId()); return getGiftOrderResponseFromGiftOrder(order); diff --git a/src/main/java/gift/service/PointService.java b/src/main/java/gift/service/PointService.java new file mode 100644 index 000000000..adf671d92 --- /dev/null +++ b/src/main/java/gift/service/PointService.java @@ -0,0 +1,39 @@ +package gift.service; + +import gift.dto.point.PointResponse; +import gift.exception.NotFoundElementException; +import gift.repository.MemberRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +public class PointService { + + private final MemberRepository memberRepository; + + public PointService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + public PointResponse addPoint(Long memberId, Integer point) { + var member = memberRepository.findById(memberId) + .orElseThrow(() -> new NotFoundElementException(memberId + "를 가진 이용자가 존재하지 않습니다.")); + member.addPoint(point); + memberRepository.save(member); + return PointResponse.of(member.getPoint()); + } + + public void subtractPoint(Long memberId, Integer point) { + var member = memberRepository.findById(memberId) + .orElseThrow(() -> new NotFoundElementException(memberId + "를 가진 이용자가 존재하지 않습니다.")); + member.subtractPoint(point); + memberRepository.save(member); + } + + public PointResponse getPoint(Long memberId) { + var member = memberRepository.findById(memberId) + .orElseThrow(() -> new NotFoundElementException(memberId + "를 가진 이용자가 존재하지 않습니다.")); + return PointResponse.of(member.getPoint()); + } +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 960e46316..66f98df58 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,7 +1,7 @@ -insert into member(email, password, deleted) -values ('member@naver.com', 'password', 0); -insert into member(email, password, deleted) -values ('admin@naver.com', 'password', 0); +insert into member(email, password, point, deleted) +values ('member@naver.com', 'password', 0, 0); +insert into member(email, password, point, deleted) +values ('admin@naver.com', 'password', 0, 0); insert into category(name, description, color, image_url, deleted) values ('디지털/가전', '가전설명', '#888888', '가전이미지', 0); diff --git a/src/test/java/gift/service/OptionServiceTest.java b/src/test/java/gift/service/OptionServiceTest.java index bf28b1860..16a61ff13 100644 --- a/src/test/java/gift/service/OptionServiceTest.java +++ b/src/test/java/gift/service/OptionServiceTest.java @@ -90,7 +90,7 @@ void failAddOptionWithZeroQuantity() { //given var optionRequest = new OptionRequest("옵션1", 0); var savedOption = optionService.addOption(1L, optionRequest); - var orderRequest = new GiftOrderRequest(savedOption.id(), 1, "hello"); + var orderRequest = new GiftOrderRequest(savedOption.id(), 1, "hello", 0); //when, then Assertions.assertThatThrownBy(() -> optionService.orderOption(savedOption.id(), orderRequest)).isInstanceOf(BadRequestException.class); @@ -101,7 +101,7 @@ void failAddOptionWithZeroQuantity() { @DisplayName("동시성 테스트 - 5개의 쓰레드풀에 500개의 요청을 보냈을 때에도 정상적으로 요청이 처리 된다.") public void concurrencyTest() throws InterruptedException { //given - var orderRequest = new GiftOrderRequest(1L, 1, "hello"); + var orderRequest = new GiftOrderRequest(1L, 1, "hello", 0); int requestCount = 500; var executorService = Executors.newFixedThreadPool(5); var countDownLatch = new CountDownLatch(requestCount);