java web搭建商城购物车
Java Web商城购物车页面和功能实现
下面我将详细介绍商城购物车模块的前后端完整实现方案。
🏗️ 系统架构设计
技术栈
-
后端: Spring Boot + Spring MVC + MyBatis/Spring Data JPA
-
前端: Thymeleaf/JSP + Bootstrap + jQuery + Ajax
-
会话管理: HttpSession 或 Redis
-
数据库: MySQL
📊 数据库设计
sql
-- 购物车表 CREATE TABLE cart (id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id BIGINT NOT NULL COMMENT '用户ID',product_id BIGINT NOT NULL COMMENT '商品ID',quantity INT NOT NULL DEFAULT 1 COMMENT '商品数量',selected TINYINT DEFAULT 1 COMMENT '是否选中:1选中,0未选中',create_time DATETIME DEFAULT CURRENT_TIMESTAMP,update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,UNIQUE KEY uk_user_product (user_id, product_id),INDEX idx_user (user_id) );-- 商品表 (已存在,这里展示相关字段) CREATE TABLE product (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(200) NOT NULL,price DECIMAL(10,2) NOT NULL,stock INT NOT NULL,status TINYINT DEFAULT 1,main_image VARCHAR(500),description TEXT );
🔧 后端实现
1. 实体类
CartItem.java
java
@Data public class CartItem {private Long id;private Long userId;private Long productId;private Integer quantity;private Integer selected;private Date createTime;private Date updateTime;// 关联商品信息private String productName;private BigDecimal productPrice;private Integer productStock;private String productImage;private Integer productStatus;// 计算小计public BigDecimal getSubtotal() {if (productPrice != null && quantity != null) {return productPrice.multiply(new BigDecimal(quantity));}return BigDecimal.ZERO;}// 检查商品是否有效public boolean isValid() {return productStatus != null && productStatus == 1 && productStock != null && productStock >= quantity;} }
CartVO.java (购物车视图对象)
java
@Data public class CartVO {private List<CartItem> cartItems;private Boolean allSelected; // 是否全选private BigDecimal totalPrice; // 总价格private Integer totalQuantity; // 总数量private Integer selectedCount; // 选中商品数量public void calculate() {if (cartItems == null) {this.totalPrice = BigDecimal.ZERO;this.totalQuantity = 0;this.selectedCount = 0;return;}this.totalPrice = BigDecimal.ZERO;this.totalQuantity = 0;this.selectedCount = 0;this.allSelected = true;for (CartItem item : cartItems) {totalQuantity += item.getQuantity();if (item.getSelected() == 1) {selectedCount++;totalPrice = totalPrice.add(item.getSubtotal());} else {allSelected = false;}}if (cartItems.isEmpty()) {allSelected = false;}} }
2. 数据访问层
CartMapper.java
java
@Mapper public interface CartMapper {// 根据用户ID查询购物车列表List<CartItem> selectByUserId(Long userId);// 根据用户ID和商品ID查询CartItem selectByUserIdAndProductId(@Param("userId") Long userId, @Param("productId") Long productId);// 新增购物车商品int insert(CartItem cartItem);// 更新购物车商品数量int updateQuantity(@Param("id") Long id, @Param("quantity") Integer quantity);// 更新选中状态int updateSelected(@Param("id") Long id, @Param("selected") Integer selected);// 批量更新选中状态int batchUpdateSelected(@Param("userIds") List<Long> ids, @Param("selected") Integer selected);// 删除购物车商品int delete(Long id);// 批量删除int batchDelete(@Param("ids") List<Long> ids);// 清空用户购物车int clearByUserId(Long userId);// 统计用户购物车商品数量int countByUserId(Long userId); }
CartMapper.xml
xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.mall.mapper.CartMapper"><resultMap id="CartItemMap" type="com.mall.model.CartItem"><id column="id" property="id"/><result column="user_id" property="userId"/><result column="product_id" property="productId"/><result column="quantity" property="quantity"/><result column="selected" property="selected"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/><!-- 关联商品信息 --><result column="product_name" property="productName"/><result column="product_price" property="productPrice"/><result column="product_stock" property="productStock"/><result column="product_image" property="productImage"/><result column="product_status" property="productStatus"/></resultMap><select id="selectByUserId" resultMap="CartItemMap">SELECT c.*, p.name as product_name, p.price as product_price, p.stock as product_stock, p.main_image as product_image,p.status as product_statusFROM cart cLEFT JOIN product p ON c.product_id = p.idWHERE c.user_id = #{userId}ORDER BY c.create_time DESC</select><select id="selectByUserIdAndProductId" resultMap="CartItemMap">SELECT c.*, p.name as product_name, p.price as product_price, p.stock as product_stock, p.main_image as product_image,p.status as product_statusFROM cart cLEFT JOIN product p ON c.product_id = p.idWHERE c.user_id = #{userId} AND c.product_id = #{productId}</select><insert id="insert" parameterType="com.mall.model.CartItem" useGeneratedKeys="true" keyProperty="id">INSERT INTO cart (user_id, product_id, quantity, selected)VALUES (#{userId}, #{productId}, #{quantity}, #{selected})</insert><update id="updateQuantity">UPDATE cart SET quantity = #{quantity}, update_time = NOW()WHERE id = #{id}</update><update id="updateSelected">UPDATE cart SET selected = #{selected}, update_time = NOW()WHERE id = #{id}</update><delete id="delete">DELETE FROM cart WHERE id = #{id}</delete><delete id="batchDelete">DELETE FROM cart WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach></delete> </mapper>
3. 服务层
CartService.java
java
public interface CartService {// 添加商品到购物车Result addToCart(Long userId, Long productId, Integer quantity);// 获取购物车列表CartVO getCartList(Long userId);// 更新购物车商品数量Result updateQuantity(Long userId, Long cartId, Integer quantity);// 更新选中状态Result updateSelected(Long userId, Long cartId, Integer selected);// 全选/取消全选Result selectAll(Long userId, Integer selected);// 删除购物车商品Result deleteCartItem(Long userId, Long cartId);// 批量删除Result batchDelete(Long userId, List<Long> cartIds);// 清空购物车Result clearCart(Long userId);// 获取购物车商品数量Integer getCartCount(Long userId); }
CartServiceImpl.java
java
@Service @Slf4j public class CartServiceImpl implements CartService {@Autowiredprivate CartMapper cartMapper;@Autowiredprivate ProductService productService;@Overridepublic Result addToCart(Long userId, Long productId, Integer quantity) {try {// 验证商品是否存在且可用Product product = productService.getProductById(productId);if (product == null || product.getStatus() != 1) {return Result.error("商品不存在或已下架");}if (product.getStock() < quantity) {return Result.error("商品库存不足");}// 检查购物车是否已有该商品CartItem existingItem = cartMapper.selectByUserIdAndProductId(userId, productId);if (existingItem != null) {// 更新数量int newQuantity = existingItem.getQuantity() + quantity;if (newQuantity > product.getStock()) {return Result.error("超过商品库存限制");}cartMapper.updateQuantity(existingItem.getId(), newQuantity);} else {// 新增商品CartItem newItem = new CartItem();newItem.setUserId(userId);newItem.setProductId(productId);newItem.setQuantity(quantity);newItem.setSelected(1);cartMapper.insert(newItem);}return Result.success("添加购物车成功");} catch (Exception e) {log.error("添加购物车失败: userId={}, productId={}", userId, productId, e);return Result.error("添加购物车失败");}}@Overridepublic CartVO getCartList(Long userId) {List<CartItem> cartItems = cartMapper.selectByUserId(userId);// 过滤无效商品cartItems = cartItems.stream().filter(CartItem::isValid).collect(Collectors.toList());CartVO cartVO = new CartVO();cartVO.setCartItems(cartItems);cartVO.calculate();return cartVO;}@Overridepublic Result updateQuantity(Long userId, Long cartId, Integer quantity) {try {CartItem cartItem = getCartItemById(cartId);if (cartItem == null || !cartItem.getUserId().equals(userId)) {return Result.error("购物车商品不存在");}Product product = productService.getProductById(cartItem.getProductId());if (product == null || product.getStatus() != 1) {return Result.error("商品已下架");}if (quantity > product.getStock()) {return Result.error("超过商品库存限制");}if (quantity <= 0) {return deleteCartItem(userId, cartId);}cartMapper.updateQuantity(cartId, quantity);return Result.success();} catch (Exception e) {log.error("更新购物车数量失败: cartId={}, quantity={}", cartId, quantity, e);return Result.error("更新失败");}}@Overridepublic Result updateSelected(Long userId, Long cartId, Integer selected) {try {CartItem cartItem = getCartItemById(cartId);if (cartItem == null || !cartItem.getUserId().equals(userId)) {return Result.error("购物车商品不存在");}cartMapper.updateSelected(cartId, selected);return Result.success();} catch (Exception e) {log.error("更新选中状态失败: cartId={}, selected={}", cartId, selected, e);return Result.error("更新失败");}}@Overridepublic Result selectAll(Long userId, Integer selected) {try {List<CartItem> cartItems = cartMapper.selectByUserId(userId);List<Long> cartIds = cartItems.stream().map(CartItem::getId).collect(Collectors.toList());if (!cartIds.isEmpty()) {cartMapper.batchUpdateSelected(cartIds, selected);}return Result.success();} catch (Exception e) {log.error("批量更新选中状态失败: userId={}, selected={}", userId, selected, e);return Result.error("操作失败");}}@Overridepublic Result deleteCartItem(Long userId, Long cartId) {try {CartItem cartItem = getCartItemById(cartId);if (cartItem != null && cartItem.getUserId().equals(userId)) {cartMapper.delete(cartId);}return Result.success("删除成功");} catch (Exception e) {log.error("删除购物车商品失败: cartId={}", cartId, e);return Result.error("删除失败");}}@Overridepublic Result batchDelete(Long userId, List<Long> cartIds) {try {if (cartIds != null && !cartIds.isEmpty()) {cartMapper.batchDelete(cartIds);}return Result.success("删除成功");} catch (Exception e) {log.error("批量删除购物车商品失败: cartIds={}", cartIds, e);return Result.error("删除失败");}}@Overridepublic Result clearCart(Long userId) {try {cartMapper.clearByUserId(userId);return Result.success("清空成功");} catch (Exception e) {log.error("清空购物车失败: userId={}", userId, e);return Result.error("清空失败");}}@Overridepublic Integer getCartCount(Long userId) {return cartMapper.countByUserId(userId);}private CartItem getCartItemById(Long cartId) {// 这里需要根据实际情况实现return null;} }
4. 控制器层
CartController.java
java
@Controller @RequestMapping("/cart") public class CartController {@Autowiredprivate CartService cartService;// 购物车页面@GetMappingpublic String cartPage(HttpSession session, Model model) {Long userId = getCurrentUserId(session);if (userId == null) {return "redirect:/login";}CartVO cartVO = cartService.getCartList(userId);model.addAttribute("cartVO", cartVO);return "cart/cart";}// 添加商品到购物车@PostMapping("/add")@ResponseBodypublic Result addToCart(HttpSession session, @RequestParam Long productId,@RequestParam(defaultValue = "1") Integer quantity) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.error("请先登录");}return cartService.addToCart(userId, productId, quantity);}// 更新商品数量@PostMapping("/updateQuantity")@ResponseBodypublic Result updateQuantity(HttpSession session,@RequestParam Long cartId,@RequestParam Integer quantity) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.error("请先登录");}return cartService.updateQuantity(userId, cartId, quantity);}// 更新选中状态@PostMapping("/updateSelected")@ResponseBodypublic Result updateSelected(HttpSession session,@RequestParam Long cartId,@RequestParam Integer selected) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.error("请先登录");}return cartService.updateSelected(userId, cartId, selected);}// 全选/取消全选@PostMapping("/selectAll")@ResponseBodypublic Result selectAll(HttpSession session,@RequestParam Integer selected) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.error("请先登录");}return cartService.selectAll(userId, selected);}// 删除购物车商品@PostMapping("/delete")@ResponseBodypublic Result deleteCartItem(HttpSession session,@RequestParam Long cartId) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.error("请先登录");}return cartService.deleteCartItem(userId, cartId);}// 批量删除@PostMapping("/batchDelete")@ResponseBodypublic Result batchDelete(HttpSession session,@RequestParam List<Long> cartIds) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.error("请先登录");}return cartService.batchDelete(userId, cartIds);}// 清空购物车@PostMapping("/clear")@ResponseBodypublic Result clearCart(HttpSession session) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.error("请先登录");}return cartService.clearCart(userId);}// 获取购物车数量(用于头部显示)@GetMapping("/count")@ResponseBodypublic Result getCartCount(HttpSession session) {Long userId = getCurrentUserId(session);if (userId == null) {return Result.success(0);}Integer count = cartService.getCartCount(userId);return Result.success(count);}private Long getCurrentUserId(HttpSession session) {// 从session中获取当前用户IDUser user = (User) session.getAttribute("currentUser");return user != null ? user.getId() : null;} }
🎨 前端页面实现
购物车页面 (cart.html)
html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"><title>购物车 - 电商商城</title><link href="/css/bootstrap.min.css" rel="stylesheet"><link href="/css/font-awesome.min.css" rel="stylesheet"><link href="/css/cart.css" rel="stylesheet"> </head> <body><!-- 头部导航 --><div th:replace="common/header :: header"></div><div class="container mt-4"><div class="card"><div class="card-header bg-white"><h4 class="mb-0"><i class="fa fa-shopping-cart"></i> 我的购物车</h4></div><div class="card-body"><!-- 购物车头部 --><div class="cart-header row align-items-center bg-light py-2"><div class="col-1 text-center"><input type="checkbox" class="form-check-input" id="selectAll" th:checked="${cartVO.allSelected}" onchange="selectAll(this.checked)"><label class="form-check-label" for="selectAll">全选</label></div><div class="col-5">商品信息</div><div class="col-2 text-center">单价</div><div class="col-2 text-center">数量</div><div class="col-1 text-center">小计</div><div class="col-1 text-center">操作</div></div><!-- 购物车商品列表 --><div class="cart-items" th:if="${cartVO.cartItems != null and !cartVO.cartItems.isEmpty()}"><div class="cart-item row align-items-center py-3 border-bottom" th:each="item : ${cartVO.cartItems}"><div class="col-1 text-center"><input type="checkbox" class="form-check-input item-checkbox" th:checked="${item.selected == 1}"th:onclick="|updateSelected(${item.id}, this.checked ? 1 : 0)|"></div><div class="col-5"><div class="d-flex"><img th:src="${item.productImage}" th:alt="${item.productName}" class="cart-item-image me-3"><div class="cart-item-info"><h6 th:text="${item.productName}" class="mb-1"></h6><small class="text-muted">库存: <span th:text="${item.productStock}"></span></small></div></div></div><div class="col-2 text-center"><span class="product-price" th:text="'¥' + ${#numbers.formatDecimal(item.productPrice, 1, 2)}"></span></div><div class="col-2 text-center"><div class="quantity-controls input-group input-group-sm" style="max-width: 120px; margin: 0 auto;"><button class="btn btn-outline-secondary" type="button" th:onclick="|updateQuantity(${item.id}, ${item.quantity} - 1)|">-</button><input type="number" class="form-control text-center quantity-input" th:value="${item.quantity}"th:onchange="|updateQuantity(${item.id}, this.value)|"min="1" th:attr="max=${item.productStock}"><button class="btn btn-outline-secondary" type="button" th:onclick="|updateQuantity(${item.id}, ${item.quantity} + 1)|">+</button></div></div><div class="col-1 text-center"><span class="subtotal fw-bold text-danger" th:text="'¥' + ${#numbers.formatDecimal(item.subtotal, 1, 2)}"></span></div><div class="col-1 text-center"><button class="btn btn-link text-danger btn-sm" th:onclick="|deleteItem(${item.id})|"><i class="fa fa-trash"></i></button></div></div></div><!-- 空购物车提示 --><div class="empty-cart text-center py-5" th:if="${cartVO.cartItems == null or cartVO.cartItems.isEmpty()}"><i class="fa fa-shopping-cart fa-4x text-muted mb-3"></i><h4 class="text-muted">购物车空空如也</h4><p class="text-muted">快去选购心仪的商品吧</p><a href="/products" class="btn btn-primary mt-3">去购物</a></div></div><!-- 购物车底部结算 --><div class="card-footer bg-white" th:if="${cartVO.cartItems != null and !cartVO.cartItems.isEmpty()}"><div class="row align-items-center"><div class="col-6"><button class="btn btn-outline-danger" onclick="batchDelete()"><i class="fa fa-trash"></i> 删除选中</button><button class="btn btn-outline-secondary ms-2" onclick="clearCart()"><i class="fa fa-broom"></i> 清空购物车</button></div><div class="col-6 text-end"><div class="d-inline-block me-4">已选择 <span class="text-danger fw-bold" th:text="${cartVO.selectedCount}">0</span> 件商品总计: <span class="total-price text-danger fw-bold fs-5" th:text="'¥' + ${#numbers.formatDecimal(cartVO.totalPrice, 1, 2)}">¥0.00</span></div><button class="btn btn-danger btn-lg" onclick="checkout()">去结算 (<span th:text="${cartVO.selectedCount}">0</span>)</button></div></div></div></div></div><!-- 底部 --><div th:replace="common/footer :: footer"></div><script src="/js/jquery.min.js"></script><script src="/js/bootstrap.bundle.min.js"></script><script>// 更新商品数量function updateQuantity(cartId, quantity) {quantity = parseInt(quantity);if (quantity < 1) quantity = 1;$.post('/cart/updateQuantity', {cartId: cartId,quantity: quantity}, function(result) {if (result.success) {location.reload();} else {alert(result.message);}});}// 更新选中状态function updateSelected(cartId, selected) {$.post('/cart/updateSelected', {cartId: cartId,selected: selected ? 1 : 0}, function(result) {if (result.success) {location.reload();} else {alert(result.message);}});}// 全选/取消全选function selectAll(selected) {$.post('/cart/selectAll', {selected: selected ? 1 : 0}, function(result) {if (result.success) {location.reload();} else {alert(result.message);}});}// 删除单个商品function deleteItem(cartId) {if (confirm('确定要删除这个商品吗?')) {$.post('/cart/delete', {cartId: cartId}, function(result) {if (result.success) {location.reload();} else {alert(result.message);}});}}// 批量删除function batchDelete() {const selectedItems = $('.item-checkbox:checked');if (selectedItems.length === 0) {alert('请选择要删除的商品');return;}if (confirm('确定要删除选中的商品吗?')) {const cartIds = [];selectedItems.each(function() {const cartId = $(this).closest('.cart-item').find('.item-checkbox').attr('onclick').match(/updateSelected\((\d+),/)[1];cartIds.push(parseInt(cartId));});$.post('/cart/batchDelete', {cartIds: cartIds}, function(result) {if (result.success) {location.reload();} else {alert(result.message);}});}}// 清空购物车function clearCart() {if (confirm('确定要清空购物车吗?')) {$.post('/cart/clear', function(result) {if (result.success) {location.reload();} else {alert(result.message);}});}}// 去结算function checkout() {const selectedCount = parseInt($('.selected-count').text());if (selectedCount === 0) {alert('请选择要结算的商品');return;}window.location.href = '/order/checkout';}// 实时更新购物车数量(头部显示)function updateCartCount() {$.get('/cart/count', function(result) {if (result.success) {$('#cartCount').text(result.data);}});}// 页面加载时更新购物车数量$(document).ready(function() {updateCartCount();});</script> </body> </html>
🎯 核心功能特性
1. 购物车管理
-
✅ 添加商品到购物车
-
✅ 查看购物车列表
-
✅ 修改商品数量
-
✅ 选中/取消选中商品
-
✅ 全选/取消全选
-
✅ 删除单个商品
-
✅ 批量删除选中商品
-
✅ 清空购物车
2. 业务逻辑
-
✅ 库存验证
-
✅ 商品状态检查
-
✅ 价格计算
-
✅ 数据一致性保证
3. 用户体验
-
✅ 实时数量更新
-
✅ 异步操作
-
✅ 错误提示
-
✅ 空状态处理
4. 样式设计 (cart.css)
css
.cart-item-image {width: 80px;height: 80px;object-fit: cover; }.quantity-controls {max-width: 120px; }.quantity-input {max-width: 50px; }.empty-cart {min-height: 300px;display: flex;flex-direction: column;justify-content: center;align-items: center; }.cart-header {font-weight: bold;border-bottom: 2px solid #dee2e6; }.total-price {font-size: 1.5rem; }.cart-item:hover {background-color: #f8f9fa; }
这个购物车模块提供了完整的前后端实现,包括数据库设计、业务逻辑、API接口和用户界面。您可以根据具体需求进行调整和扩展。