本文詳細記錄購物車模塊的資料庫設計、實體類(含 BigDecimal 精確計算)、DAO/Service 核心邏輯、前端 jQuery 互動(增刪改查、全選、批量刪除、結算)完整代碼。
不使用 Session/Cookie,採用資料庫表持久化,支援跨裝置、登入後合併購物車。
CREATE TABLE `t_cartitem` (
`cartItemId` char(32) NOT NULL COMMENT '主鍵',
`quantity` int(11) NOT NULL COMMENT '數量',
`bid` char(32) NOT NULL COMMENT '圖書ID',
`uid` char(32) NOT NULL COMMENT '用戶ID',
`orderBy` int(11) NOT NULL COMMENT '排序',
PRIMARY KEY (`cartItemId`),
KEY `FK_cartitem_book` (`bid`),
KEY `FK_cartitem_user` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

public class CartItem {
private String cartItemId;
private int quantity;
private Book book;
private User user;
// 使用 BigDecimal 避免浮點數誤差
public double getSubtotal() {
BigDecimal price = new BigDecimal(book.getCurrPrice() + "");
BigDecimal qty = new BigDecimal(quantity + "");
return price.multiply(qty).doubleValue();
}
// getter/setter 略
}
public List<CartItem> findByUser(String uid) throws SQLException {
String sql = "SELECT * FROM t_cartitem c, t_book b WHERE c.bid=b.bid AND uid=? ORDER BY c.orderBy";
List<Map<String,Object>> mapList = qr.query(sql, new MapListHandler(), uid);
return toCartItemList(mapList);
}
public void add(CartItem cartItem) {
try {
// 1. 檢查是否已存在相同圖書
CartItem existing = cartItemDao.findByUidAndBid(cartItem.getUser().getUid(), cartItem.getBook().getBid());
if (existing == null) {
cartItem.setCartItemId(CommonUtils.uuid());
cartItemDao.addCartItem(cartItem);
} else {
// 2. 存在則合併數量
int newQty = existing.getQuantity() + cartItem.getQuantity();
cartItemDao.updateQuantity(existing.getCartItemId(), newQty);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 修改數量
public void updateQuantity(String cartItemId, int quantity) {
cartItemDao.updateQuantity(cartItemId, quantity);
}
// 批量刪除
public void batchDelete(String cartItemIds) {
String[] ids = cartItemIds.split(",");
for (String id : ids) {
cartItemDao.delete(id);
}
}
// 結算:載入勾選的條目
public List<CartItem> loadCartItems(String cartItemIds) {
String[] ids = cartItemIds.split(",");
String placeholders = String.join(",", Collections.nCopies(ids.length, "?"));
String sql = "SELECT * FROM t_cartitem c, t_book b WHERE c.bid=b.bid AND c.cartItemId IN (" + placeholders + ")";
return qr.query(sql, new MapListHandler(), (Object[]) ids);
}
完整代碼涵蓋:總計計算、全選/反選、數量增減(AJAX)、批量刪除、結算提交。
<script>
$(function() {
showTotal(); // 初始化總計
// 全選
$("#selectAll").click(function() {
var checked = $(this).prop("checked");
$(":checkbox[name=checkboxBtn]").prop("checked", checked);
setJieSuan(checked);
showTotal();
});
// 單選聯動全選
$(":checkbox[name=checkboxBtn]").click(function() {
var all = $(":checkbox[name=checkboxBtn]").length;
var checked = $(":checkbox[name=checkboxBtn]:checked").length;
$("#selectAll").prop("checked", all === checked);
setJieSuan(checked > 0);
showTotal();
});
// 減號
$(".jian").click(function() {
var id = $(this).attr("id").substring(0, 32);
var qty = parseInt($("#" + id + "Quantity").val());
if (qty === 1) {
if (confirm("確定刪除該條目?")) {
location = "/goods/CartItemServlet?method=batchDelete&cartItemIds=" + id;
}
} else {
sendUpdateQuantity(id, qty - 1);
}
});
// 加號
$(".jia").click(function() {
var id = $(this).attr("id").substring(0, 32);
var qty = parseInt($("#" + id + "Quantity").val());
sendUpdateQuantity(id, qty + 1);
});
});
// AJAX 更新數量
function sendUpdateQuantity(id, qty) {
$.ajax({
url: "/goods/CartItemServlet",
data: {method:"updateQuantity", cartItemId:id, quantity:qty},
type: "POST", dataType: "json",
success: function(res) {
$("#" + id + "Quantity").val(res.quantity);
$("#" + id + "Subtotal").text(res.subtotal);
showTotal();
}
});
}
// 計算總計
function showTotal() {
var total = 0;
$(":checkbox[name=checkboxBtn]:checked").each(function() {
var id = $(this).val();
total += Number($("#" + id + "Subtotal").text());
});
$("#total").text(round(total, 2)); // round.js 保留兩位小數
}
// 結算提交
function jiesuan() {
var ids = [];
$(":checkbox[name=checkboxBtn]:checked").each(function() {
ids.push($(this).val());
});
$("#cartItemIds").val(ids.join(","));
$("#hiddenTotal").val($("#total").text());
$("#jieSuanForm").submit();
}
</script>
function round(num, dec) {
var str = num + "", idx = str.indexOf(".");
if (idx < 0) return num;
var curDec = str.length - idx - 1;
if (dec < curDec) {
var factor = Math.pow(10, dec);
return Math.round(num * factor) / factor;
}
return num;
}

購物車模塊展示了資料庫持久化、精確金額計算、AJAX 異步互動、前後端協作的完整閉環,是電商系統的核心組件。