首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> JSP 程式設計 >> 關於JSP

JSP 購物車核心模型設計與添加商品功能完整實現

     編輯:關於JSP

購物車業務流程分析

網上購物的核心流程:用戶瀏覽商品 → 加入購物車 → 查看/修改/刪除購物項 → 確認結算 → 填寫收貨信息 → 生成訂單 → 支付 → 訂單管理。購物車作為臨時存儲,需支撐:

  • 跨請求持久化:用戶瀏覽多頁面過程中購物車內容不丟失。
  • 動態數據結構:商品種類、數量不固定,頻繁增刪改查。
  • 關鍵屬性封裝:每項包含商品對象、購買數量、小計金額。

技術選型:Session 存儲 + ArrayList + 領域模型

決策點方案理由
存儲位置HttpSession用戶級隔離、生命週期與會話綁定、無需資料庫開銷、支援集群 Session 複製
容器類型ArrayList<Item>有序、隨機訪問、動態擴容、迭代性能優於 LinkedList
項結構Item(Goods goods, int quantity)組合模式,封裝商品信息與購買屬性,單一職責

領域模型類定義

Goods 商品實體

package com.shop.domain;

import java.io.Serializable;
import java.math.BigDecimal;

public class Goods implements Serializable {
    private String goodsId;      // 主鍵
    private String goodsName;    // 商品名稱
    private BigDecimal price;    // 單價 (金額必用 BigDecimal)
    private int stock;           // 庫存
    private String imageUrl;     // 圖片
    private String description;  // 描述
    
    // 標準 Getter/Setter、equals/hashCode (按 goodsId)、toString
    // 兩個分頁查詢輔助方法 (略)
}

Item 購物項 (值對象)

package com.shop.cart;

import com.shop.domain.Goods;
import java.io.Serializable;
import java.math.BigDecimal;

public class Item implements Serializable {
    private Goods goods;
    private int quantity;
    
    public Item(Goods goods, int quantity) {
        this.goods = goods;
        this.quantity = quantity;
    }
    
    public Goods getGoods() { return goods; }
    public void setGoods(Goods goods) { this.goods = goods; }
    public int getQuantity() { return quantity; }
    public void setQuantity(int quantity) { this.quantity = quantity; }
    
    // 衍生屬性:小計
    public BigDecimal getSubtotal() {
        return goods.getPrice().multiply(BigDecimal.valueOf(quantity));
    }
    
    // equals/hashCode 基於 goodsId,便於 List.contains/remove 判斷
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Item)) return false;
        Item item = (Item) o;
        return goods != null && goods.getGoodsId().equals(item.goods.getGoodsId());
    }
    @Override public int hashCode() { return goods != null ? goods.getGoodsId().hashCode() : 0; }
}

CartManager 購物車業務組件 (無狀態、可序列化)

package com.shop.cart;

import com.shop.domain.Goods;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class CartManager implements Serializable {
    private List items = new ArrayList<>();

    public List getItems() { return items; }
    public void setItems(List items) { this.items = items; }

    // 核心:添加商品 (自動合併同款)
    public void addItem(Goods goods, int quantity) {
        if (goods == null || quantity <= 0) return;
        
        // 庫存校驗 (可選,視業務嚴格度)
        // if (quantity > goods.getStock()) quantity = goods.getStock();
        
        Optional existing = items.stream()
            .filter(item -> item.equals(new Item(goods, 0))) // 利用 equals 比較 goodsId
            .findFirst();
        
        if (existing.isPresent()) {
            existing.get().setQuantity(existing.get().getQuantity() + quantity);
        } else {
            items.add(new Item(goods, quantity));
        }
    }

    // 便利方法:獲取總金額
    public BigDecimal getTotalPrice() {
        return items.stream()
            .map(Item::getSubtotal)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    
    // 便利方法:總件數
    public int getTotalQuantity() {
        return items.stream().mapToInt(Item::getQuantity).sum();
    }
    
    // 是否為空
    public boolean isEmpty() { return items.isEmpty(); }
}

商品列表頁面:加入購物車入口

<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="https://java.sun.com/jsp/jstl/fmt" %>
<div class="goods-grid">
    <c:forEach items="${goodsPage.records}" var="goods">
        <div class="goods-card">
            <img src="${goods.imageUrl}" alt="${goods.goodsName}">
            <h3>${goods.goodsName}</h3>
            <p class="price">¥<fmt:formatNumber value="${goods.price}" type="currency" currencySymbol=""/></p>
            <p class="stock">庫存:${goods.stock}</p>
            <form action="${pageContext.request.contextPath}/cart/add" method="post">
                <input type="hidden" name="goodsId" value="${goods.goodsId}"/>
                <input type="number" name="quantity" value="1" min="1" max="${goods.stock}" class="qty">
                <button type="submit" class="btn-add-cart">加入購物車</button>
            </form>
        </div>
    </c:forEach>
</div>

<!-- 分頁導航 -->
<c:url value="/goods/list" var="pageUrl">
    <c:param name="page" value="${goodsPage.pageNum - 1}"/>
</c:url>
<a href="${pageUrl}">上一頁</a> ...

Controller 處理添加請求

@WebServlet("/cart/add")
public class AddToCartController extends HttpServlet {
    @Autowired GoodsService goodsService;

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String goodsId = req.getParameter("goodsId");
        int qty = Integer.parseInt(req.getParameter("quantity"));
        
        // 1. 獲取/創建購物車
        HttpSession session = req.getSession(true);
        CartManager cart = (CartManager) session.getAttribute("cart");
        if (cart == null) {
            cart = new CartManager();
            session.setAttribute("cart", cart);
        }
        
        // 2. 查詢商品 (含庫存、價格快照)
        Goods goods = goodsService.findById(goodsId);
        if (goods == null) {
            resp.sendRedirect(req.getHeader("Referer") + "?error=商品不存在");
            return;
        }
        
        // 3. 添加至購物車
        cart.addItem(goods, qty);
        
        // 4. 重定向回來源頁 (PRG 模式防重複提交)
        String referer = req.getHeader("Referer");
        resp.sendRedirect(referer != null ? referer : req.getContextPath() + "/goods/list");
    }
}

購物車查看頁面

<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="https://java.sun.com/jsp/jstl/fmt" %>
<table class="cart-table">
    <thead>
        <tr><th>商品</th><th>單價</th><th>數量</th><th>小計</th><th>操作</th></tr>
    </thead>
    <tbody>
        <c:forEach items="${cart.items}" var="item" varStatus="st">
            <tr>
                <td>
                    <img src="${item.goods.imageUrl}" width="50">
                    ${item.goods.goodsName}
                </td>
                <td>¥<fmt:formatNumber value="${item.goods.price}" type="number" minFractionDigits="2"/></td>
                <td>${item.quantity}</td>
                <td>¥<fmt:formatNumber value="${item.subtotal}" type="number" minFractionDigits="2"/></td>
                <td>
                    <a href="${pageContext.request.contextPath}/cart/remove?goodsId=${item.goods.goodsId}">移除</a>
                </td>
            </tr>
        </c:forEach>
    </tbody>
    <tfoot>
        <tr>
            <td colspan="3">總計</td>
            <td><strong>¥<fmt:formatNumber value="${cart.totalPrice}" type="number" minFractionDigits="2"/></strong></td>
            <td><a href="${pageContext.request.contextPath}/cart/checkout">去結算</a></td>
        </tr>
    </tfoot>
</table>

關鍵設計細節與最佳實踐

  1. BigDecimal 處理金額:絕不使用 `float/double`,避免浮點數精度丟失導致金額錯誤。
  2. Item.equals 基於 goodsId:使 `List.contains()`、`remove()` 正確識別同一商品,實現自動合併數量。
  3. PRG 模式 (Post-Redirect-Get):添加操作用 POST,處理完重定向 GET,防止用戶刷新頁面重複提交。
  4. Session 同步:集群環境需配置 Session 複製 (Sticky Session / Redis Session / Database Session),確保購物車跨節點可用。
  5. 購物車合併策略:用戶登入時,將 Session 購物車與資料庫持久化購物車合併 (按數量取大或相加),登出時可選擇保留或清空。
  6. 庫存預佔 vs 實時扣減:加入購物車時可不扣減庫存,下單時再樂觀/悲觀鎖扣減;高併發秒殺場景需預佔庫存 (Redis Lua 腳本)。
  7. 安全防護:CSRF Token 保護表單、XSS 輸出轉義 (`${fn:escapeXml()}`)、SQL 注入參數化。

總結

JSP 購物車核心在於:**Session 作用域存儲 `CartManager`,`ArrayList` 承載動態商品列表,`Item` 封裝 `Goods` 與 `quantity` 並提供 `subtotal` 衍生屬性,`CartManager` 提供 `addItem` 等業務操作**。Controller 薄調度,Service 查詢商品,JSP+JSTL/EL 渲染。此模型清晰、擴展性強,為後續修改數量、刪除、結算生成訂單奠定堅實基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved