当前位置: 首页 > news >正文

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接口和用户界面。您可以根据具体需求进行调整和扩展。

http://www.dtcms.com/a/430604.html

相关文章:

  • 从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 6--基础知识 2--常用元素定位 2
  • 从“端到端”到“人到人”:一种以需求直接满足为核心的新一代人机交互范式
  • C到C++(Num015)
  • 做关于车的网站有哪些网页布局的方式有哪些
  • 图漾相机C++语言---Sample_V1(4.X.X版本)完整参考例子(待完善)
  • Python数据挖掘之基础分类模型_支持向量机(SVM)
  • Java-Spring 入门指南(十六)SpringMVC--RestFul 风格
  • 益阳网站制作公司地址高端装饰公司网站设计
  • 产生式规则在自然语言处理深层语义分析中的演变、影响与未来启示
  • K230基础-摄像头的使用
  • 【文件读写】绕过验证下
  • 谷歌官方网站注册12306铁路网站开发语言
  • 深度学习基础知识-深度神经网络基础
  • pycharm找不到Tencent Cloud CodeBuddy如何安装[windows]?pycharm插件市场找不到插件如何安装?
  • 【开题答辩全过程】以 SpringbootVueUniapp农产品展销平台为例,包含答辩的问题和答案
  • C++中的小数及整数位填充
  • DuckDB 的postgresql插件无法访问GooseDB
  • 电子商务软件网站建设的核心网站布局模板
  • 从Nginx到Keepalived:反向代理高可用的技术闭环——Nginx、Keepalived、VIP与VRRP的深度联动解析
  • 现场运维指南
  • 查看和修改Linux的主机名称
  • Vmware虚拟机联网问题,显示:线缆已拔出!!!
  • 部署Nginx(Kylinv10sp3、Ubuntu2204、Rocky9.3)
  • 【含文档+PPT+源码】基于微信小程序的房屋租赁系统
  • GitHub 热榜项目 - 日榜(2025-10-01)
  • linux的文件和目录操作函数
  • 网站首页psdwordpress禁用修订
  • Coze源码分析-资源库-编辑工作流-后端源码-领域/数据访问/基础设施层
  • 13个GNS3 3.0.5 appliances设备模板镜像合集:IOSv/L2,IOU L2/L3,以及IOS-XE
  • Java-Spring入门指南(十九)thymeleaf基本概念