일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자동차수리시스템
- 예외미루기
- 한국건설관리시스템
- 환경설정
- 사용자예외클래스생성
- abstract
- 메소드오버로딩
- 자바
- oracle
- exception
- cursor문
- 인터페이스
- 제네릭
- 집합_SET
- 대덕인재개발원
- 다형성
- 참조형변수
- 정수형타입
- 오라클
- Java
- 컬렉션 타입
- GRANT VIEW
- 객체 비교
- 컬렉션프레임워크
- EnhancedFor
- 생성자오버로드
- NestedFor
- 예외처리
- 추상메서드
- 어윈 사용법
- Today
- Total
거니의 velog
(45) 장바구니 API 만들기 3 _ 수정 본문
4. 장바구니 서비스 계층의 설계/구현
* 장바구니 서비스는 service 패키지 내에 CartService와 CartServiceImpl로 구현한다.
* CartService에는 장바구니 아이템을 추가하거나 수정하는 기능이 CartItemDTO를 이용하므로 하나의 메서드로 설계하고 사용자의 장바구니 아이템들의 조회와 장바구니 아이템의 삭제 기능을 선언한다.
package com.unlimited.mallapi.service;
import java.util.List;
import org.springframework.transaction.annotation.Transactional;
import com.unlimited.mallapi.dto.CartItemDTO;
import com.unlimited.mallapi.dto.CartItemListDTO;
/**
* 장바구니 서비스를 정의한 인터페이스입니다.
*
* @Transactional: 해당 인터페이스의 모든 메서드에 대한 트랜잭션 처리를 지정하는 어노테이션
*/
@Transactional(rollbackFor = Exception.class)
public interface CartService {
/**
* 장바구니에 상품을 추가 또는 수정하는 메서드입니다.
*
* @param cartItemDTO 장바구니 상품 정보를 담은 DTO
* @return 업데이트된 장바구니 상품 목록
*/
public List<CartItemListDTO> addOrModify(CartItemDTO cartItemDTO);
/**
* 특정 회원의 장바구니 상품 목록을 조회하는 메서드입니다.
*
* @param email 조회할 회원의 이메일
* @return 특정 회원의 장바구니 상품 목록
*/
public List<CartItemListDTO> getCartItems(String email);
/**
* 장바구니 상품을 삭제하는 메서드입니다.
*
* @param cino 삭제할 장바구니 상품의 고유 식별 번호
* @return 삭제 후의 장바구니 상품 목록
*/
public List<CartItemListDTO> remove(Long cino);
}
* 메서드들의 리턴 타입이 모두 List<CartItemListDTO> 인 것은 장바구니 아이템을 처리한 후에는 화면에 새로 갱신해야 하는 장바구니 아이템들의 데이터가 필요하기 때문이다.
* CartServiceImpl에서의 구현은 아래와 같다.
package com.unlimited.mallapi.service;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import com.unlimited.mallapi.domain.Cart;
import com.unlimited.mallapi.domain.CartItem;
import com.unlimited.mallapi.domain.Member;
import com.unlimited.mallapi.domain.Product;
import com.unlimited.mallapi.dto.CartItemDTO;
import com.unlimited.mallapi.dto.CartItemListDTO;
import com.unlimited.mallapi.repository.CartItemRepository;
import com.unlimited.mallapi.repository.CartRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
/**
* 장바구니 서비스 구현 클래스입니다.
*
* @Service: 해당 클래스가 서비스 역할을 한다는 것을 스프링에 알려주는 어노테이션
* @RequiredArgsConstructor: 필요한 모든 final 필드에 대한 생성자를 자동으로 생성해주는 롬복 어노테이션
* @Log4j2: 롬복을 사용하여 로깅을 위한 로거를 자동으로 생성해주는 어노테이션
*/
@RequiredArgsConstructor
@Service
@Log4j2
public class CartServiceImpl implements CartService {
private final CartRepository cartRepository;
private final CartItemRepository cartItemRepository;
/**
* 장바구니에 상품을 추가 또는 수정하는 메서드입니다.
*
* @param cartItemDTO 장바구니 상품 정보를 담은 DTO
* @return 업데이트된 장바구니 상품 목록
*/
@Override
public List<CartItemListDTO> addOrModify(CartItemDTO cartItemDTO) {
String email = cartItemDTO.getEmail(); // 사용자 이메일
Long pno = cartItemDTO.getPno(); // 상품 고유 번호
int qty = cartItemDTO.getQty(); // 상품 수량
Long cino = cartItemDTO.getCino(); // 장바구니 상품 고유 번호
log.info("======================================================");
log.info(cartItemDTO.getCino() == null);
if (cino != null) { // 장바구니 아이템 번호가 있어서 수량만 변경하는 경우
Optional<CartItem> cartItemResult = cartItemRepository.findById(cino);
CartItem cartItem = cartItemResult.orElseThrow();
cartItem.changeQty(qty);
cartItemRepository.save(cartItem);
return getCartItems(email);
}
// 장바구니 아이템 번호 cino가 없는 경우
// 사용자의 카트
Cart cart = getCart(email);
CartItem cartItem = null;
// 이미 동일한 상품이 담긴적이 있을 수 있으므로
cartItem = cartItemRepository.getItemOfPno(email, pno);
if (cartItem == null) {
Product product = Product.builder().pno(pno).build();
cartItem = CartItem.builder().product(product).cart(cart).qty(qty).build();
} else {
cartItem.changeQty(qty);
}
// 상품 아이템 저장
cartItemRepository.save(cartItem);
return getCartItems(email);
}
// 사용자의 장바구니가 없었다면 새로운 장바구니를 생성하고 반환
private Cart getCart(String email) {
Cart cart = null;
Optional<Cart> result = cartRepository.getCartOfMember(email);
if (result.isEmpty()) {
log.info("Cart of the member is not exist!!");
Member member = Member.builder().email(email).build();
Cart tempCart = Cart.builder().owner(member).build();
cart = cartRepository.save(tempCart);
} else {
cart = result.get();
}
return cart;
}
/**
* 특정 회원의 장바구니 상품 목록을 조회하는 메서드입니다.
*
* @param email 조회할 회원의 이메일
* @return 특정 회원의 장바구니 상품 목록
*/
@Override
public List<CartItemListDTO> getCartItems(String email) {
return cartItemRepository.getItemsOfCartDTOByEmail(email);
}
/**
* 장바구니 상품을 삭제하는 메서드입니다.
*
* @param cino 삭제할 장바구니 상품의 고유 식별 번호
* @return 삭제 후의 장바구니 상품 목록
*/
@Override
public List<CartItemListDTO> remove(Long cino) {
Long cno = cartItemRepository.getCartFromItem(cino);
log.info("cart no: " + cno);
cartItemRepository.deleteById(cino);
return cartItemRepository.getItemsOfCartDTOByCart(cno);
}
}
5. 컨트롤러 계층과 테스트
* 장바구니 관련 기능은 controller 패키지에 CartController를 추가해서 구현한다. 외부에서 호출하는 경로는 /api/cart/ 로 시작하도록 구현한다. /api/member 를 제외하면 모든 기능은 JWTCheckFilter를 거치기 때문에 CartController는 스프링 시큐리티 관련 기능을 사용하도록 구현한다.
package com.unlimited.mallapi.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.unlimited.mallapi.service.CartService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@RestController
@RequiredArgsConstructor
@Log4j2
@RequestMapping("/api/cart")
public class CartController {
private final CartService cartService;
}
* CartController는 CartService 타입의 객체를 주입받도록 설계한다.
(1) 장바구니 아이템의 추가/수정
* 장바구니 아이템의 추가/수정에서는 장바구니 아이템의 수량(qty)에 중점을 두고 코드를 작성한다. 만일 수량이 0보다 작은 상태가 되면 실제로는 삭제로 처리하고 장바구니 아이템 목록을 반환한다.
package com.unlimited.mallapi.controller;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.unlimited.mallapi.dto.CartItemDTO;
import com.unlimited.mallapi.dto.CartItemListDTO;
import com.unlimited.mallapi.service.CartService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
/**
* 장바구니 관련 요청을 처리하는 컨트롤러 클래스입니다.
*
* @RestController: 해당 클래스가 RESTful API 요청을 처리하는 컨트롤러임을 나타내는 어노테이션
* @RequiredArgsConstructor: 필요한 모든 final 필드에 대한 생성자를 자동으로 생성해주는 롬복 어노테이션
* @Log4j2: 롬복을 사용하여 로깅을 위한 로거를 자동으로 생성해주는 어노테이션
* @RequestMapping: 클래스 레벨에서 요청 URL을 지정하는 어노테이션
*/
@RestController
@RequiredArgsConstructor
@Log4j2
@RequestMapping("/api/cart")
public class CartController {
private final CartService cartService;
/**
* 장바구니에 상품을 추가하거나 수정하는 메서드입니다.
*
* @param itemDTO 장바구니 상품 정보를 담은 DTO
* @return 업데이트된 장바구니 상품 목록
*/
@PreAuthorize("#itemDTO.email == authentication.name")
@PostMapping("/change")
public List<CartItemListDTO> changeCart(@RequestBody CartItemDTO itemDTO) {
log.info(itemDTO);
if (itemDTO.getQty() <= 0) {
return cartService.remove(itemDTO.getCino());
}
return cartService.addOrModify(itemDTO);
}
}
* changeCart() 에서는 현재 로그인한 사용자의 이메일과 파라미터로 전달된 CartItemDTO의 이메일 주소가 같아야만 호출이 가능하도록 @PreAuthorize 표현식을 적용한다. 만일 두 이메일 정보가 일치하지 않는다면 접근 권한이 없는(Access Denied) 상황으로 처리된다.
* 근데? 오류가 뜨는 상황이라서 일단 @PreAuthorize("permitAll()") 로 접근 권한을 다 열어 놓았으니 일단 추후에 수정 바람....
* 테스트를 위해서는 우선 /api/member/login 을 이용해서 로그인을 통해 만들어지는 Access Token을 활용해야 한다.
* /api/cart/change 를 호출할 때 Authorization 헤더를 지정한다.
* 특정한 상품으로 추가하는 경우 장바구니 아이템의 번호가 없는 상태가 되므로 이를 JSON으로 전송한다.
* 상품번호(pno)를 변경하면 새로운 상품이 추가되는지 확인한다.
* 만일 장바구니 아이템 번호가 전달되면 이를 이용하므로 상품번호는 없어도 무방하다. 예를 들어 아래의 왼쪽과 같이 3번 아이템의 수량(qty)을 변경하고자 했다면 아래의 그림과 같이 cino 값을 전달하면 된다.
(2) 사용자의 장바구니 목록
* 사용자의 장바구니 목록은 /api/cart/items 로 조회할 수 있도록 구성한다. 호출 시에는 시큐리티를 통해서 사용자의 인증 정보를 이용하도록 구성한다.
/**
* 현재 사용자의 장바구니 상품 목록을 조회하는 메서드입니다.
*
* import java.security.Principal;
*
* @param principal 현재 사용자를 나타내는 Principal 객체
* @return 장바구니 상품 목록
*/
@PreAuthorize("hasAnyRole('ROLE_USER')")
@GetMapping("/items")
public List<CartItemListDTO> getCartItems(Principal principal) {
// Principal 객체를 이용하여 현재 사용자의 이메일을 가져옵니다.
String email = principal.getName();
log.info("--------------------------------------------");
log.info("email: " + email);
// 현재 사용자의 이메일을 이용하여 장바구니 상품 목록을 조회합니다.
return cartService.getCartItems(email);
}
* getItems() 의 파라미터로 java.security.Principal 타입을 지정하는데 이를 이용하면 현재 사용자의 정보에 접근이 가능하다. 주로 다운캐스팅해서 사용하거나 예제와 같이 getName()을 이용해서 username(예제에서는 이메일) 값을 파악할 때 사용한다.
* Postman에서는 별도의 파라미터의 전달없이 GET 방식으로 확인이 가능하다.
(3) 장바구니 아이템의 삭제
* 장바구니 아이템의 삭제는 장바구니 아이템 번호(cino)를 이용해서 DELETE 방식으로 호출하는 경우에 동작하게 한다. 다만, 실제 해당 수량(qty)이 0 이하가 된다면 삭제와 동일한 의미이기 때문에 실제로 직접 호출될 가능성은 많지 않다.
/**
* 장바구니에서 특정 상품을 제거하는 메서드입니다.
*
* @param cino 장바구니 상품 번호
* @return 업데이트된 장바구니 상품 목록
*/
@PreAuthorize("hasAnyRole('ROLE_USER')")
@DeleteMapping("/{cino}")
public List<CartItemListDTO> removeFromCart(@PathVariable("cino") Long cino) {
log.info("장바구니 상품 번호: " + cino);
return cartService.remove(cino);
}
* 장바구니는 시큐리티와 같이 접목되기 때문에 각 기능에 대해서 테스트를 반드시 실행해서 동작에 문제가 없는지 확인한 후에 리액트 개발을 이어가야만 한다.
'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글
(47) 리액트 장바구니 구성 2 (0) | 2024.03.08 |
---|---|
(46) 리액트 장바구니 구성 1 (0) | 2024.03.08 |
(44) 장바구니 API 만들기 2 (0) | 2024.03.08 |
(43) 장바구니 API 만들기 1 (0) | 2024.03.08 |
(42) 리액트 소셜 로그인 5 (0) | 2024.03.08 |