SpringBoot 배달의민족-7 장바구니 기능 구현

hanteng·2022년 5월 24일
0

오늘은 장바구니 기능에 대해 구현해보도록 하겠습니다

구현하기전에 고려할 사항에 대해 한번 정리해보자면 제일 먼저 사용자가 특정 메뉴를
장바구니에 추가할시 어떤 매장의 메뉴를 추가했는지와 몇개를 주문했는지 옵션은
뭘 선택했는지를 알아야합니다. 또 이미 장바구니에 추가된 메뉴가 있을시에 현재 장바구니에
추가하려고 하는 메뉴와 같은 매장인지 아닌지를 비교하여 다른 매장일시에는
기존에 추가한 메뉴를 장바구니에서 삭제해줘야 합니다

그 다음으로 장바구니에 추가한 메뉴를 어디에 저장할지를 생각해야 합니다. DB에 장바구니
테이블을 생성하여 관리할시에 사용자가 장바구니에 메뉴를 추가할때마다 DB에 쿼리문을
날려야하며 비회원일경우 회원ID가 존재하지 않기 때문에 테이블에 저장할수가 없습니다
물론 비회원에게 강제로 ID를 할당하는 방법이 존재하지만 여전히 DB와 통신횟수가 늘어나는
문제가 발생하기 때문에 우리는 장바구니 정보를 Session에 저장하도록 할겁니다

일단 사용자가 장바구니에 추가할 메뉴의 정보를 받기 위해 Dto를 하나 추가합니다

CartDto.java 전체코드

@Data
public class CartDto {
	
	private long foodId;
	private String foodName;
	private int foodPrice;
	private int amount;
    private int totalPrice;
 
	private String[] optionName;
	private int[] optionPrice;
	private long[] optionId;
	
	public void totalPriceCalc() {
		int temp_Price = 0;
		if(optionPrice != null) {
			for(int price : optionPrice) {
				temp_Price += price;
			}
		}
		this.totalPrice = (temp_Price + foodPrice) * amount;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(optionId);
		result = prime * result + Arrays.hashCode(optionName);
		result = prime * result + Arrays.hashCode(optionPrice);
		result = prime * result + Objects.hash(foodId, foodName, foodPrice);
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		CartDto other = (CartDto) obj;
		return foodId == other.foodId && Objects.equals(foodName, other.foodName) && foodPrice == other.foodPrice
				&& Arrays.equals(optionId, other.optionId) && Arrays.equals(optionName, other.optionName)
				&& Arrays.equals(optionPrice, other.optionPrice);
	}
	
}

장바구니에 추가할 메뉴는 한번에 1개씩만 가능합니다 하지만 추가옵션의 경우는
한메뉴당 여러개가 존재할수 있습니다 따라서 우리는 옵션과 관련된 부분은 배열로
받아 처리해야 합니다
다음으로 사용자는 장바구니에 하나의 메뉴가 아니라 여러개의 메뉴를 담을수 있어야 합니다
또한 장바구니에 담긴 메뉴들이 어떤 매장에서 추가한것인지도 알아야 하므로
매장의 정보와 CartDto를 List로 가지고 있는 새로운 Dto를 하나 추가해줍니다

totalPriceCalc()는 선택한 메뉴의 총 가격을 구하기 위한 메서드고
hashCode()와 equals()는 기존에 장바구니에 있는 메뉴중에 현재 추가하려는 메뉴가
존재하는지를 확인하기 위한 메서드입니다 ( 추가옵션까지 비교 )

CartListDto.java 전체코드

@Data
@AllArgsConstructor
public class CartListDto {
	private long storeId;
	private String storeName;
	int cartTotal;
	private int deliveryTip;
	
	List<CartDto> cartDto;
}

이제 사용자가 장바구니에 특정 메뉴를 추가할시 데이터를 받아 처리하기 위해
api패키지안에 컨트롤러를 하나 추가해주세요

CartApiController.java 전체코드

@RestController
public class CartApiController {
	
	
	// 장바구니 추가
	@PostMapping("/api/cart")
	public CartListDto addCart(CartDto cartDto, long storeId, String storeName, int deliveryTip, HttpSession session) {
		
		//기존 Session에서 저장된 장바구니 목록을 가져옴
		CartListDto cartListDto = (CartListDto) session.getAttribute("cartList");
		
		cartDto.totalPriceCalc();
		//Session에 저장된 장바구니 목록이 없을시
		if(cartListDto == null) {
			List<CartDto> newCart = new ArrayList<>();
			newCart.add(cartDto);
			cartListDto = new CartListDto(storeId, storeName, cartDto.getTotalPrice(), deliveryTip, newCart);
			
		} else { //저장된 장바구니 목록이 있을시
			List<CartDto> prevCart = cartListDto.getCartDto();
			int prevCartTotal = cartListDto.getCartTotal();
			cartListDto.setCartTotal(prevCartTotal + cartDto.getTotalPrice());
			
			// 이미 장바구니에 추가된 메뉴일때 
			if(prevCart.contains(cartDto)) {
				int cartIndex = prevCart.indexOf(cartDto);
				int amount = cartDto.getAmount();
				
				CartDto newCart = prevCart.get(cartIndex);
				int newAmount = newCart.getAmount() + amount;
				
				newCart.setAmount(newAmount);
				newCart.totalPriceCalc();
				prevCart.set(cartIndex, newCart);
				
			} else { // 장바구니에 추가되어 있지 않은 메뉴일때
				prevCart.add(cartDto);
			}
		}
		
		session.setAttribute("cartList", cartListDto);
        
        System.out.println("cartList = " + cartListDto);
 
		return cartListDto;
    
    
    //상세페이지에 접근했을시 장바구니 목록
	@GetMapping("/api/cart")
	public CartListDto cartList(HttpSession session) {
		CartListDto cartList = (CartListDto) session.getAttribute("cartList");
		if (cartList != null) {
			return cartList;
		}
		return null;
	}
    
    
    //장바구니 전체 삭제
    @DeleteMapping("/api/cart")
	public void deleteAllCart(HttpSession session) {
		session.removeAttribute("cartList");
	}
 	
    //장바구니 1개 삭제
	@DeleteMapping("/api/cart/{index}")
	public CartListDto deleteOneCart(@PathVariable int index, HttpSession session) {
		CartListDto cartList = (CartListDto) session.getAttribute("cartList");
		if (cartList == null) {
			return null;
		}
		int cartTotal = cartList.getCartTotal();
		List<CartDto> cart = cartList.getCartDto();
		int removeCartPrice = cart.get(index).getTotalPrice();
		
		cart.remove(index);
		
		if(cart.size() == 0) {
			session.removeAttribute("cartList");
			return null;
		}
		
		cartTotal -=  removeCartPrice;
		cartList.setCartTotal(cartTotal);
		return cartList;
	}
    
	
}

장바구니에 메뉴를 추가하려고 할때 먼저 Session에 저장된 목록이 있는지 확인해야
합니다 존재하지 않을시에는 CartListDto를 생성하여 Session에 추가하고 존재할시에는
장바구니에 존재하는 메뉴들중에 현재 추가하고자 하는 메뉴와 추가옵션까지 동일한
항목이 존재하는지 확인해야 합니다 메뉴는 동일하지만 추가옵션이 다를경우에는
다른 항목이라고 생각하고 리스트에 추가하고 추가옵션까지 같을 경우에는 리스트에서
기존의 아이템을 찾아 수량만 변경해주도록 합니다

우리는 장바구니에 추가된 메뉴들을 사용자의 화면에 뿌려줄때 List에 저장된 메뉴를
차례대로 추가해줬습니다. 따라서 특정 메뉴를 삭제할때는 index번호를 통해
Session에 저장된 CartDto List에서 index번째 아이템을 지우고 최종금액에서
해당 아이템의 금액을 빼주면 됩니다

이제 서버에서의 처리는 모두 구현이 완료되었습니다. 위에서 말한 모든 기능을
처리할수 있도록 js파일에 밑의 코드를 추가해줍시다

StoreDetail.js 전체코드

const mindelivery = Number($("#min_delivery").data("min_delivery"));
const deliveryTip = Number($("#delivery_tip").data("delivery_tip"));
const storeId = $("#store_id").val();
const storeName = $("#store_name").data("store_name");

const cart = (function(){
	// 장바구니에 담긴 가게번호 (다른가게에서 담은 상품인지 확인) 
	let cartStoreId = null;
	const getCartStoreId = function(){
		return cartStoreId;
	}
	const setCartStoreId = function(set){
		cartStoreId = set;
	}
	// 장바구니에 담긴 상품 수
	let cartSize = 0;
	
	const getCartSize = function(){
		return cartSize;
	}
	
	const setCartSize = function(set){
		cartSize = set;
	}
	
	
	// 장바구니에 담은 메뉴가격 총합
	let menuTotalPrice = 0;
	
	const getMenuTotalPrice = function(){
		return menuTotalPrice;
	}
	
	const setMenuTotalPrice = function(set){
		menuTotalPrice = set;
	}


	
	return {
		getCartStoreId : getCartStoreId, 
		setCartStoreId : setCartStoreId,
		getCartSize : getCartSize,
		setCartSize : setCartSize,
		getMenuTotalPrice : getMenuTotalPrice,
		setMenuTotalPrice : setMenuTotalPrice,
		};
})();


// 장바구니 담기
$(".add_cart").click(function(){
	const cartStoreId = cart.getCartStoreId();
	if(cartStoreId != null && storeId != cartStoreId ) {
		swal({
			buttons: ["취소", "담기"],
			title: "장바구니에는 같은 가게의 메뉴만 담을 수 있습니다",
			text: "선택하신 메뉴를 장바구니에 담을 경우 이전에 담은 메뉴가 삭제됩니다"
		})
		.then((value) => {
			if (value == true) {
				deleteCartAll();
				addCart($(this));
			}
		});			
	} else {
		addCart($(this));
	}
}) // 장바구니 담기

	
function addCart(addCart){
	// 선택한 추가옵션 배열에 저장
	const foodOptionName = [];
	const foodOptionPrice = [];
	const foodOptionId = [];
		
	// 선택된 추가옵션 가져오기 
	$("input[name='option']:checked").each(function() {
		const optionName = $(this).val();
		const optionId = $(this).siblings(".option_id").val();
		const optionPrice = $(this).siblings(".option_price").val();  
		
		foodOptionName.push(optionName);
		foodOptionId.push(optionId);
		foodOptionPrice.push(optionPrice);
	})
	
	const data = {
		foodId : addCart.siblings(".add_cart_food_id").val(),
		foodName : addCart.siblings(".add_cart_food_name").val(),
		foodPrice : addCart.siblings(".add_cart_food_price").val(),
		amount : addCart.parent().siblings(".modal_box").find("#amount").val(),
		optionName : foodOptionName,
		optionId : foodOptionId,
		optionPrice : foodOptionPrice,
		deliveryTip : deliveryTip,
		storeId : storeId, 
		storeName : storeName
	}
	
	$.ajax({
		url: "/api/cart",
		type: "post",
		data : data,
		traditional : true
	})
	.done(function(result){
		cartList(result);
		
		alarm();	
		closeModal();
		$("#amount").val(1);
		
		// 밖에 있으니 작동이 안되서 추가
		$(document).on("click", ".cancle_btn", function() {
			const index = $(this).parent().index();
			deleteCartOne(index);
		}); // 장바구니 1개 삭제
		
	}) // done
	.fail(function(){
		swal("에러가 발생했습니다");
	}) // ajax
} // addCart
	
	
function alarm(text) {
	$(".alarm").text(text);
	
	$(".alarm").show();
	setTimeout(function(){
		$(".alarm").hide();
	},1000);
}

function cartList(result){
	const cartList = result.cartDto;
	const storeId = result.storeId;
	const storeName = result.storeName;
	const cartTotal = result.cartTotal;
	cart.setCartSize(cartList.length);
	
	let html = "";
		
	for(var i=0;i<cartList.length;i++) {
		let optionHtml = "";
		if(cartList[i].optionName != null ) {
			for(var j=0;j<cartList[i].optionName.length;j++) {
				const optionName = cartList[i].optionName[j];
				const optionPrice = Number(cartList[i].optionPrice[j]).toLocaleString();
				
				optionHtml += `<div class="cart_menu_option"> ${optionName }  ${optionPrice }원</div>`;
			}
		}
		
		html += `<li> 
					<h3>${cartList[i].foodName  }</h3>
					<div>${cartList[i].foodPrice.toLocaleString()}원</div>
					<div>수량 : ${cartList[i].amount }</div>
					<div>${optionHtml} </div>
					<div>합계 : ${cartList[i].totalPrice.toLocaleString() }원</div>
					<button class="cancle_btn"> ${"x"} </button>
				</li>`; 
				 // 장바구니 추가하면 장바구니 리스트 변경
		
		
	}
	cart.setMenuTotalPrice(cartTotal);
	cart.setCartStoreId(storeId );
	
	$(".cart ul").html(html);
	$(".total").html("총 합계 : " + cartTotal.toLocaleString() + "원");
	$(".m_cart_count").css("display" , "block");
	$(".m_cart_count").text(cartList.length);

	
	mindeliveryCheck();
}

// 주문금액이 최소주문금액 이상이어야 주문가능
function mindeliveryCheck() {
	const menuTotalPrice = cart.getMenuTotalPrice();
	
	if(mindelivery <= menuTotalPrice) {
		$(".order_btn").attr("disabled", false); 
		$(".order_btn").css("background", "#30DAD9");
		$(".order_btn").text("주문하기");
	} else {
		$(".order_btn").css("background", "#ddd");
		$(".order_btn").attr("disabled", true); 
		$(".order_btn").text(mindelivery + "원 이상 주문할 수 있습니다");
	}
}


// 주문하기
$(".order_btn").click(function() {
	location.href = "/order";
});
    
   
   


// 장바구니 1개 삭제
$(document).on("click", ".cancle_btn", function() {
	const index = $(this).parent().index();
	deleteCartOne(index);
}); 


// 장바구니 1개 삭제
function deleteCartOne(index){
	$.ajax({
		url: `/api/cart/${index}`,
		type: "delete",
	})
	.done(function(result){
		if(result == "") {
			cartReset();
			return;
		}
		cartList(result);
		$(".m_cart_count").css("display" , "block");
		$(".m_cart_count").text(result.cartDto.length);
	})
	.fail(function(){
		swal("에러가 발생했습니다");
	})
}

//장바구니 모두 삭제
function deleteCartAll(){
	$.ajax({
		url: "/api/cart",
		type: "delete"
	})
	.done(function(){
		cartReset();
	})
	.fail(function(){
		swal("에러가 발생했습니다");
	})
}


// 가게 입장시 카트리스트 불러오기
(function(){
	$.ajax({
		url: "/api/cart",
		type: "get"
	})
	.done(function(result){
		if(result == "" ) {
			//cartReset();
			return;
		}
		cartList(result);
	})
	.fail(function(){
		swal("장바구니 정보 에러");
	})
})();

function cartReset() {
	$(".cart ul").html("");
	$(".total").html("장바구니가 비었습니다.");
	$(".order_btn").css("background", "#ddd");
	$(".order_btn").attr("disabled", true); 
	$(".order_btn").text("주문하기");
	$(".m_cart_count").css("display" , "none");
	$(".m_cart_count").text("");
	
	cart.setCartSize(0);
	cart.setMenuTotalPrice(0);
};

코드를 간단하게 설명하자면 사용자가 특정 메뉴를 장바구니에 추가버튼을 누를때
사용자가 선택한 메뉴와 수량, 추가옵션과 현재 접근한 매장의 기본적인 정보를
서버로 보내고 서버와의 통신에 성공해 cartList를 result로 받게 되면 화면에
나타나고 있는 장바구니의 모든 메뉴를 삭제하고 cartList(result)를 통해 장바구니 목록
구역에 동적으로 현재 사용자가 가지고 있는 모든 메뉴의 목록을 새로 추가합니다.

장바구니의 모든 메뉴를 삭제할땐 단순히 서버로 요청만을 보내고
1개의 메뉴만 삭제시킬땐 주소에 index를 포함시켜 보내도록 합니다

매장 상세 페이지에서는 장바구니에 저장된 모든 메뉴를 보여줘야 하기 때문에
상세페이지 진입시에 Session에 저장된 데이터를 받기 위해 get요청을 하며
받아온 데이터를 동적으로 추가해줍니다

profile
이메일 : ehfvndcjstk@naver.com

4개의 댓글

comment-user-thumbnail
2022년 7월 20일

장바구니 추가를 누르면
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'long'; nested exception :
For input string: "imgpizza.png"
즉 dl_food 테이블의 foodImg에서 입력받은 값을 jsp에서 출력하면서 에러가 나는 것 같은데 어느부분을 수정해야 하는지 알려주시면 감사하겠습니다. 참고로 저는 oracle을 사용하지 않고 mysql을 사용하고 있습니다.
위와 같은 에러가 납니다.

1개의 답글
comment-user-thumbnail
2023년 10월 16일

안녕하세요! 저는 thymeleaf랑 springboot로 쇼핑몰 구현하고 있는데요, js에서는 session storage로 접근이 가능한데 spring boot에서는 아무리 session storage에 저장해도 값이 안들어가서요ㅜㅜ
스프링 시큐리티 보안때문인지 뭐때문인지, 구글링해도 못찾겠고, 이 포스팅에서는 사용하신것 같아서 여쭤봅니다..!

답글 달기