Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Tag(name = "장바구니 API")
public interface CartController {

@Operation(summary = "회원 장바구니 조회", description = "회원이 장바구니에 들어갈 때 사용되는 API")
ResponseEntity<CartResponse> getCartItems(User user);

@Operation(summary = "회원 장바구니 추가", description = "회원이 장바구니에 물건을 담을 때 사용되는 API")
ResponseEntity<CartDetailResponse> addCartItem(User user, AddCartRequest request);
ResponseEntity<CartResponse> addCartItem(User user, List<AddCartRequest> request);

@Operation(summary = "회원 장바구니 수량 변경", description = "회원이 장바구니 내에서 수량 변경을 할 때 사용되는 API")
ResponseEntity<CartDetailResponse> updateCartItem(User user, UpdateCartRequest request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ public ResponseEntity<CartResponse> getCartItems(@CurrentUser User user) {
//장바구니 상품 추가
@Override
@PostMapping
public ResponseEntity<CartDetailResponse> addCartItem(@CurrentUser User user, @RequestBody @Valid AddCartRequest request) {
CartDetailResponse cartDetailResponse = cartService.addCartItem(user, request);
public ResponseEntity<CartResponse> addCartItem(@CurrentUser User user, @RequestBody List<AddCartRequest> request) {
CartResponse cartResponse = cartService.addCartItem(user, request);

return ResponseEntity.ok(cartDetailResponse);
return ResponseEntity.ok(cartResponse);
}

//장바구니 상품 업데이트
Expand Down
3 changes: 3 additions & 0 deletions backend/JiShop/src/main/java/com/jishop/cart/domain/Cart.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public class Cart extends BaseEntity {

private int quantity;

@Version
private Long version;

@Builder
public Cart(User user, SaleProduct saleProduct, int quantity) {
this.user = user;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
public interface CartService {

CartResponse getCart(User user);
CartDetailResponse addCartItem(User user, AddCartRequest addCartRequest);
CartResponse addCartItem(User user, List<AddCartRequest> addCartRequest);
CartDetailResponse updateCart(User user, UpdateCartRequest updateCartRequest);
void removeCartItem(User user, DeleteCartRequest deleteCartRequest);
CartResponse getGuestCart(List<GuestCartRequest> guestCartRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import com.jishop.saleproduct.repository.SaleProductRepository;
import com.jishop.stock.domain.Stock;
import lombok.RequiredArgsConstructor;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -40,45 +42,72 @@ public CartResponse getCart(User user) {
//장바구니 상품 추가
@Override
@Transactional
public CartDetailResponse addCartItem(User user, AddCartRequest addCartRequest) {
SaleProduct saleProduct = saleProductRepository.findById(addCartRequest.saleProductId())
.orElseThrow(()->new DomainException(ErrorType.PRODUCT_NOT_FOUND));

int requestQuantity = addCartRequest.quantity();
Stock stock = saleProduct.getStock();

Cart cart = cartRepository.findByUserAndSaleProduct(user, saleProduct).orElse(null);

// 이미 장바구니에 상품이 존재하는 경우
if (cart != null) {
// isForced가 false인 경우, 기존 상품 정보만 반환하고 수량은 업데이트하지 않음
if (!addCartRequest.isForced()) {
return CartDetailResponse.of(cart, true);
public CartResponse addCartItem(User user, List<AddCartRequest> addCartRequests) {
int maxRetries = 3;
int attempt = 0;

while(true){
try{
List<Cart> cartsToSave = new ArrayList<>();

for (AddCartRequest request : addCartRequests) {
SaleProduct saleProduct = saleProductRepository.findById(request.saleProductId())
.orElseThrow(() -> new DomainException(ErrorType.PRODUCT_NOT_FOUND));

int requestQuantity = request.quantity();
Stock stock = saleProduct.getStock();

Cart cart = cartRepository.findByUserAndSaleProduct(user, saleProduct).orElse(null);

// 이미 장바구니에 상품이 존재하는 경우
if (cart != null) {
// isForced가 false인 경우, 수량을 업데이트하지 않음
if (!request.isForced()) {
continue;
}

// isForced가 true인 경우, 기존 수량 + 새로운 수량으로 업데이트
int totalQuantity = cart.getQuantity() + requestQuantity;

// 재고 체크
if (!stock.hasStock(totalQuantity))
throw new DomainException(ErrorType.INSUFFICIENT_STOCK);

cart.updateQuantity(totalQuantity);
cartsToSave.add(cart);
} else {
// 장바구니에 상품이 없는 경우
// 재고 체크
if (!stock.hasStock(requestQuantity))
throw new DomainException(ErrorType.INSUFFICIENT_STOCK);

cart = Cart.builder()
.user(user)
.saleProduct(saleProduct)
.quantity(requestQuantity)
.build();
cartsToSave.add(cart);
}
}
if(!cartsToSave.isEmpty())
cartRepository.saveAll(cartsToSave);

//성공적으로 끝났으면 while문 탈출
break;
} catch(ObjectOptimisticLockingFailureException e){
//충돌 났을 때
if(++attempt >= maxRetries)
throw new DomainException(ErrorType.CART_OPERATION_FAILED);
}

// isForced가 true인 경우, 기존 수량 + 새로운 수량으로 업데이트
int totalQuantity = cart.getQuantity() + requestQuantity;

// 재고 체크
if(!stock.hasStock(totalQuantity))
throw new DomainException(ErrorType.INSUFFICIENT_STOCK);

cart.updateQuantity(requestQuantity);
} else {
// 장바구니에 상품이 없는 경우
// 재고 체크
if(!stock.hasStock(requestQuantity))
throw new DomainException(ErrorType.INSUFFICIENT_STOCK);

cart = Cart.builder()
.user(user)
.saleProduct(saleProduct)
.quantity(requestQuantity)
.build();
cartRepository.save(cart);
}

return CartDetailResponse.of(cart, false);
// 모든 상품을 추가/업데이트한 후 전체 장바구니 정보를 반환
List<Cart> updatedCarts = cartRepository.findCartsWithProductAndOptionByUser(user);
List<CartDetailResponse> cartDetailResponses = updatedCarts.stream()
.map(cart -> CartDetailResponse.of(cart, false))
.toList();

return CartResponse.of(cartDetailResponses);
}

// 장바구니 수량 수정
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public enum ErrorType {

// CART
CART_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "장바구니 상품을 찾을 수 없습니다."),
CART_OPERATION_FAILED(HttpStatus.CONFLICT, "장바구니 처리 중 오류가 발생했습니다."),

// VALIDATION
VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "값을 잘못 입력했습니다."),
Expand All @@ -84,7 +85,7 @@ public enum ErrorType {
DEFAULT_ADDRESS_REQUIRED(HttpStatus.NOT_FOUND, "기본 배송지가 꼭 필요합니다."),

// REDISSON
LOCK_ACQUISITION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "주문 저리 중 잠금 획득에 실패했습니다. 잠시 후 시도해주세요"),
LOCK_ACQUISITION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "주문 처리 중 잠금 획득에 실패했습니다. 잠시 후 시도해주세요"),
CONCURRENT_ORDER_PROCESSING(HttpStatus.INTERNAL_SERVER_ERROR, "동시에 너무 많은 주문이 처리되고 있습니다. 잠시 후 다시 시도해주세요"),

// QUESTION
Expand Down