iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Software Development

我的SpringBoot絕學:7+2個專案,從新手變專家系列 第 21

Day21 第六個Spring Boot專案:小型電商購物車系統(7)購物車實作

  • 分享至 

  • xImage
  •  

Entity

Cart

接著來完成購物車區塊,先建立Cart entity,包含id、購物車是誰的、買了哪些東西、總價是多少、買了幾個。

@Entity
public class Cart {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<CartItem> cartItems = new HashSet<>();
    private Integer totalPrice;
    private Integer totalQuantity;

    public Cart() {

    }

    public Cart(Long id, User user, Set<CartItem> cartItems, Integer totalPrice, Integer totalQuantity) {
        this.id = id;
        this.user = user;
        this.cartItems = cartItems;
        this.totalPrice = totalPrice;
        this.totalQuantity = totalQuantity;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Set<CartItem> getCartItems() {
        return cartItems;
    }

    public void setCartItems(Set<CartItem> cartItems) {
        this.cartItems = cartItems;
    }

    public Integer getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(Integer totalPrice) {
        this.totalPrice = totalPrice;
    }

    public Integer getTotalQuantity() {
        return totalQuantity;
    }

    public void setTotalQuantity(Integer totalQuantity) {
        this.totalQuantity = totalQuantity;
    }
}

這邊出現一些新的annotation,從user的部分開始介紹

  • @OneToOne代表的是一對一,這個entity會對應到另一個entity。
  • @JoinColumn表示我們會在Cart的資料表中,多增加一列用來儲存用戶id的數值,這一列的名字是user_id,不能是null。

下一個是cartItems

@OneToMany代表一對多的關係,因為一個購物車裡面可以有很多物品。

  • mappedBy = "cart”

表示cart這邊不去處理與CartItem間的關聯,而是交給CartItem處理關聯。

因此,cart資料表不會出現cartItem_id的欄位。

  • CascadeType.ALL

表示對Cart entity的任何操作都會傳播到CartItem entity。

目的是清空購物車時,購物車中的物品也一併刪除。

  • orphanRemoval = true

如果我們在cart刪除購物車物品,那麼cartItem的內容也會被刪除。

CartItem

建立CartItem entity,內容有id、購物車、商品、價格、數量。

@Entity
public class CartItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JsonIgnore
    private Cart cart;

    @ManyToOne
    private Product product;
    private Integer price;
    private Integer quantity;

    public CartItem() {

    }

    public CartItem(Long id, Cart cart, Product product, Integer price, Integer quantity) {
        this.id = id;
        this.cart = cart;
        this.product = product;
        this.price = price;
        this.quantity = quantity;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Cart getCart() {
        return cart;
    }

    public void setCart(Cart cart) {
        this.cart = cart;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }
}
  • @ManyToOne

表示多對一的關係

  • @JsonIgnore

表示不會在JSON輸出中看到這部分的內容。

Repository

CartRepository提供根據用戶的id找尋用戶的購物車的功能,需要自行編寫SQL語句,Spring Data JPA無法產生我們需要的程式碼。

public interface CartRepository extends JpaRepository<Cart, Long> {
    @Query("SELECT c FROM Cart c WHERE c.user.id = :userId")
    public Cart findCartByUserId(@Param("userId") Long userId);
}

CartItemRepository可以查看購物車,是否已經有同樣的商品了,可以避免重複加入。

public interface CartItemRepository extends JpaRepository<CartItem, Long> {
    @Query("SELECT ci FROM CartItem ci WHERE ci.cart = :cart AND ci.product = :product")
    public CartItem isCartItemInCart(@Param("cart") Cart cart, @Param("product") Product product);
}

Service

CartService

CartService.java由以下幾個部分組成

  • 建立購物車
public Cart createCart(User user) {
        Cart cart = new Cart();
        cart.setUser(user);
        return cartRepository.save(cart);
    }

修改AuthController.java,在建立用戶時,同時也建立新用戶專屬的購物車。

@PostMapping("/signup")
    public ResponseEntity<AuthResponse> createUserHandler(@RequestBody User user) throws Exception {
			...
			Cart cart = cartService.createCart(userService.findUserByEmail(user.getEmail()));
			return new ResponseEntity<>(authResponse, HttpStatus.CREATED);
    }
  • 將商品加入購物車,先查詢購物車內是否已經有了同樣的商品。沒有的話,創建一個新的CartItem後,把商品加入購物車。
    public void addToCart(Long userId, AddItemRequest req) throws Exception{
        Cart cart = cartRepository.findCartByUserId(userId);
        Product product = productService.getProductById(req.getProductId());
        CartItem isPresent = cartItemService.isCartItemInCart(cart, product);
        if(isPresent == null) {
            CartItem cartItem = new CartItem();
            cartItem.setCart(cart);
            cartItem.setProduct(product);
            cartItem.setQuantity(req.getQuantity());
            cartItem.setPrice(req.getQuantity() * product.getPrice());

            CartItem createdCartItem = cartItemService.createCartItem(cartItem);
            cart.getCartItems().add(createdCartItem);
            
            calcCartTotal(userId);
        }
    }

我們定義了加入購物車的請求的格式,新增AddItemRequest.java

public class AddItemRequest {
    private Long productId;
    private Integer quantity;

    public AddItemRequest(){

    }

    public AddItemRequest(Long productId, Integer quantity) {
        this.productId = productId;
        this.quantity = quantity;
    }

    public Long getProductId() {
        return productId;
    }

    public void setProductId(Long productId) {
        this.productId = productId;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }
}
  • 計算購物車內的商品數量和價格
    public Cart calcCartTotal(Long userId) {
        Cart cart = cartRepository.findCartByUserId(userId);
        int totalPrice = 0, totalQuantity = 0;

        for(CartItem cartItem : cart.getCartItems()) {
            totalPrice += cartItem.getPrice();
            totalQuantity += cartItem.getQuantity();
        }

        cart.setTotalPrice(totalPrice);
        cart.setTotalQuantity(totalQuantity);
        return cartRepository.save(cart);
    }
  • 清空購物車中的商品,重新設定購物車的總價格、總商品數為0。
public Integer clearCart(Long userId) throws Exception {
        Cart cart = cartRepository.findCartByUserId(userId);
        Integer totalPrice = cart.getTotalPrice();

        Iterator<CartItem> iterator = cart.getCartItems().iterator();
        while (iterator.hasNext()) {
            CartItem cartItem = iterator.next();
            cartItemService.removeCartItem(userId, cartItem.getId());
            iterator.remove();
        }

        cart.setTotalPrice(0);
        cart.setTotalQuantity(0);
        cartRepository.save(cart);

        return totalPrice;
    }

CartItemService

CartItemService有這幾種功能

  • 檢查商品是否在購物車中
    public CartItem isCartItemInCart(Cart cart, Product product) {
        return cartItemRepository.isCartItemInCart(cart, product);
    }
  • 創建並儲存cartItem到資料庫中,如果數量小於0,將數量設為1,以及計算價格。
public CartItem createCartItem(CartItem cartItem) {
        cartItem.setQuantity(Math.max(cartItem.getQuantity(), 1));
        cartItem.setPrice(cartItem.getProduct().getPrice() * cartItem.getQuantity());

        return cartItemRepository.save(cartItem);
    }
  • 更新CartItem,重新計算數量和價格,並儲存到資料庫。在更新前會確認發送請求的用戶和購物車的擁有者是否相同,避免修改到別人的購物車。
    public CartItem updateCartItem(Long userId, Long id, CartItem cartItem) throws Exception {
        CartItem item = findCartItemById(id);
        User user = userService.findUserById(item.getCart().getUser().getId());
        if(user.getId().equals(userId)) {
            item.setQuantity(cartItem.getQuantity());
            item.setPrice(item.getQuantity() * item.getProduct().getPrice());
        }

        return cartItemRepository.save(item);
    }

在UserService.java,新增以下程式碼,這樣才能用id查詢用戶。

public User findUserById(Long id) throws Exception{
        Optional<User> opt = userRepository.findById(id);
        if(opt.isPresent()){
            return opt.get();
        }
        throw new Exception("Error: User not found with id: " + id);
    }
  • 用ID查詢CartItem,如果CartItem不存在,就顯示例外,未找到指定id的CartItem。
public CartItem findCartItemById(Long id) throws Exception {
        Optional<CartItem> optionalCartItem = cartItemRepository.findById(id);
        if(optionalCartItem.isPresent()) {
            return optionalCartItem.get();
        }
        throw new Exception("CartItem not found with id : " + id);
    }
  • 移除購物車的商品,會檢查是不是屬於該用戶的購物車。如果購物車內的物品不存在,則提醒找不到這樣物品。
    public void removeCartItem(Long userId, Long id) throws Exception {
        CartItem item = findCartItemById(id);
        User user = userService.findUserById(item.getCart().getUser().getId());
        User reqUser = userService.findUserById(userId);
        if(user.getId().equals(reqUser.getId())) {
            cartItemRepository.deleteById(id);
            return;
        }
        throw new Exception("Can't remove another users item");
    }

Controller

CartController

CartController有兩個作用

第一個是取得購物車的內容。

@GetMapping("/")
    public ResponseEntity<Cart> findUserCart(@RequestHeader("Authorization") String jwt) throws Exception {
        User user = userService.findUserByJWT(jwt);
        Cart cart = cartService.calcCartTotal(user.getId());
        return new ResponseEntity<>(cart, HttpStatus.OK);
    }

第二個是點擊加入購物車時,將商品加入購物車。

@PutMapping("/add")
    public ResponseEntity<String> addItemToCart(@RequestBody AddItemRequest req, @RequestHeader("Authorization") String jwt) throws Exception{
        User user = userService.findUserByJWT(jwt);
        cartService.addToCart(user.getId(), req);
        return new ResponseEntity<>("Item added to cart",HttpStatus.OK);
    }

CartItemController

CartItemController提供

  • 修改商品數量。在知名網站中,點擊加號或減號,增加或減少商品數量,就是在執行和這段類似的程式碼。
    @PutMapping("/{cartItemId}")
    public ResponseEntity<String> updateCartItem(@PathVariable("cartItemId") Long id,
                                                 @RequestBody CartItem cartItem,
                                                 @RequestHeader("Authorization") String jwt) throws Exception {
        User user = userService.findUserByJWT(jwt);
        cartItemService.updateCartItem(user.getId(), id, cartItem);

        return new ResponseEntity<>("Cart item updated successfully", HttpStatus.OK);
    }
  • 刪除商品的功能
    @DeleteMapping("/{cartItemId}")
    public ResponseEntity<String> deleteCartItem(@PathVariable("cartItemId") Long id,
                                                 @RequestHeader("Authorization") String jwt) throws Exception{
        User user = userService.findUserByJWT(jwt);
        cartItemService.removeCartItem(user.getId(), id);
        return new ResponseEntity<>("CartItem deleted successfully", HttpStatus.OK);
    }

測試

  1. 先將商品加入購物車

  1. 再來查看用戶的購物車

  1. 修改購物車的商品數量,從11個變成1919個

購物車的商品真的變成1919個了

  1. 刪除購物車的商品


上一篇
Day20 第六個Spring Boot專案:小型電商購物車系統(6)商品與篩選功能
下一篇
Day22 第六個Spring Boot專案:小型電商購物車系統(8)串接Stripe API支付與使用SSL
系列文
我的SpringBoot絕學:7+2個專案,從新手變專家31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言