1. 项目概述
电商平台的购物车功能是连接商品浏览与订单支付的关键环节,直接影响用户转化率。基于Django框架实现购物车页面的商品增删改查功能,需要兼顾前后端交互的流畅性与数据一致性。本文将详细拆解从商品加入购物车到批量操作的全流程实现方案。
2. 技术架构设计
2.1 模型层设计
购物车系统采用经典的主从表结构:
class CartItem(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='cart_items') sku = models.ForeignKey('products.SKU', on_delete=models.CASCADE) quantity = models.PositiveIntegerField(default=1) is_checked = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = [['user', 'sku']] # 避免重复添加关键设计考量:
- 与用户表建立外键关联,实现多用户隔离
- 关联SKU最小库存单位,记录商品规格信息
- 使用quantity字段存储购买数量,设置正整数校验
- 通过is_checked字段实现商品勾选状态管理
- 联合唯一索引防止同一商品重复添加
2.2 接口路由规划
采用RESTful风格设计URL结构:
urlpatterns = [ path('', views.cart_list, name='cart_list'), path('add/', views.cart_add, name='cart_add'), path('update/<int:item_id>/', views.cart_update, name='cart_update'), path('delete/<int:item_id>/', views.cart_delete, name='cart_delete'), path('batch_delete/', views.cart_batch_delete, name='cart_batch_delete'), path('check/<int:item_id>/', views.cart_check, name='cart_check'), path('check_all/', views.cart_check_all, name='cart_check_all'), ]路由特点:
- 使用动词前缀明确操作类型
- 资源ID通过URL参数传递
- 命名路由便于前端反向解析
- 批量操作使用独立端点
3. 核心功能实现
3.1 商品加入购物车
@require_POST def cart_add(request): sku_id = request.POST.get('sku_id') quantity = int(request.POST.get('quantity', 1)) sku = get_object_or_404(SKU, pk=sku_id) if quantity > sku.stock: return JsonResponse({'ok': False, 'msg': f'库存不足(仅剩 {sku.stock} 件)'}) cart_item, created = CartItem.objects.get_or_create( user=request.user, sku=sku, defaults={'quantity': quantity} ) if not created: # 已存在则累加数量 new_quantity = cart_item.quantity + quantity if new_quantity > sku.stock: return JsonResponse({'ok': False, 'msg': f'库存不足(当前已有 {cart_item.quantity} 件)'}) cart_item.quantity = new_quantity cart_item.save() return JsonResponse({ 'ok': True, 'cart_count': request.user.cart_items.count() })关键实现细节:
- 使用get_or_create实现幂等操作
- 库存校验前置,避免超卖
- 已存在商品自动数量累加
- 返回当前购物车商品总数用于更新UI
3.2 商品数量修改
@require_POST def cart_update(request, item_id): cart_item = get_object_or_404(CartItem, pk=item_id, user=request.user) action = request.POST.get('action') # 'increase'/'decrease'/'set' # 计算新数量 if action == 'increase': new_qty = cart_item.quantity + 1 elif action == 'decrease': new_qty = cart_item.quantity - 1 elif action == 'set': new_qty = int(request.POST.get('quantity')) # 边界校验 if new_qty < 1: return JsonResponse({'ok': False, 'msg': '数量不能小于1'}) if new_qty > cart_item.sku.stock: return JsonResponse({'ok': False, 'msg': '超过库存上限'}) # 更新数据 cart_item.quantity = new_qty cart_item.save() # 重新计算总价 checked_items = request.user.cart_items.filter(is_checked=True) total_price = sum(item.sku.price * item.quantity for item in checked_items) return JsonResponse({ 'ok': True, 'new_quantity': new_qty, 'subtotal': cart_item.sku.price * new_qty, 'total_price': total_price })注意事项:
- 支持三种操作方式:递增/递减/直接设置
- 数量修改需同步更新小计和总价
- 边界值校验确保数据合法性
- 使用select_related优化关联查询
4. 前端交互实现
4.1 购物车列表页
采用Bootstrap5构建响应式表格:
<table class="table table-hover"> <thead> <tr> <th><input type="checkbox" id="check-all"></th> <th>商品信息</th> <th>单价</th> <th>数量</th> <th>小计</th> <th>操作</th> </tr> </thead> <tbody> {% for item in cart_items %} <tr id="cart-item-{{ item.id }}"> <td><input type="checkbox" class="item-checkbox" >function postJSON(url, data, callback) { fetch(url, { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams(data) }) .then(response => response.json()) .then(callback) .catch(console.error); } // 示例:数量增加操作 document.querySelectorAll('.btn-plus').forEach(btn => { btn.addEventListener('click', function() { const itemId = this.dataset.itemId; postJSON(`/cart/update/${itemId}/`, {action: 'increase'}, data => { if (data.ok) { updateUI(data); // 更新界面元素 } else { alert(data.msg); } }); }); });5. 性能优化实践
5.1 数据库查询优化
- 使用select_related预取外键关联:
CartItem.objects.filter(user=request.user).select_related('sku')- 批量操作使用事务保证原子性:
@transaction.atomic def cart_batch_delete(request): item_ids = request.POST.getlist('item_ids[]') CartItem.objects.filter(pk__in=item_ids, user=request.user).delete() # ...5.2 前端性能提升
- 事件委托减少监听器数量:
document.getElementById('cart-table').addEventListener('click', function(e) { if (e.target.classList.contains('btn-plus')) { // 处理增加操作 } });- 防抖处理数量输入:
const debounce = (fn, delay) => { let timer; return function() { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, arguments), delay); }; }; quantityInput.addEventListener('input', debounce(function() { // 发送更新请求 }, 500));6. 异常处理与边界情况
6.1 库存超卖防护
采用双层校验机制:
- 前端实时显示可用库存
- 后端提交时再次校验:
if new_quantity > cart_item.sku.stock: return JsonResponse({'ok': False, 'msg': '库存不足'})6.2 并发控制
使用select_for_update锁定记录:
with transaction.atomic(): cart_item = CartItem.objects.select_for_update().get(pk=item_id) # 处理数量变更7. 扩展功能实现
7.1 全选/反选逻辑
@require_POST def cart_check_all(request): checked = request.POST.get('checked') == 'true' request.user.cart_items.update(is_checked=checked) total_price = sum( item.sku.price * item.quantity for item in request.user.cart_items.all() ) if checked else 0 return JsonResponse({ 'ok': True, 'total_price': total_price, 'checked_count': request.user.cart_items.count() if checked else 0 })7.2 批量删除实现
@require_POST def cart_batch_delete(request): item_ids = request.POST.getlist('item_ids[]') if item_ids: CartItem.objects.filter(pk__in=item_ids, user=request.user).delete() return JsonResponse({ 'ok': True, 'msg': f'已删除{len(item_ids)}件商品' })8. 项目部署建议
- 缓存策略:
from django.core.cache import cache def get_cart_count(user): cache_key = f'cart_count_{user.id}' count = cache.get(cache_key) if count is None: count = user.cart_items.count() cache.set(cache_key, count, timeout=60*5) # 5分钟缓存 return count- 异步任务处理:
from celery import shared_task @shared_task def async_update_cart(item_id, quantity): try: item = CartItem.objects.get(pk=item_id) item.quantity = quantity item.save() except CartItem.DoesNotExist: pass9. 测试用例设计
9.1 单元测试示例
class CartTests(TestCase): def setUp(self): self.user = User.objects.create_user(username='test', password='test') self.sku = SKU.objects.create(name="Test", price=100, stock=10) def test_add_item(self): self.client.login(username='test', password='test') response = self.client.post('/cart/add/', {'sku_id': self.sku.id}) self.assertEqual(response.json()['ok'], True) self.assertEqual(self.user.cart_items.count(), 1) def test_stock_check(self): item = CartItem.objects.create(user=self.user, sku=self.sku, quantity=5) response = self.client.post('/cart/update/1/', {'action': 'set', 'quantity': 15}) self.assertEqual(response.json()['ok'], False)10. 项目经验总结
- 性能优化点:
- 使用prefetch_related优化多对多关系查询
- 添加数据库索引加速查询:
class Meta: indexes = [ models.Index(fields=['user', 'is_checked']), ]- 前端交互优化:
- 添加加载状态提示
- 实现动画过渡效果
- 本地存储临时数据
- 安全防护措施:
- CSRF令牌校验
- 权限验证装饰器
- 请求方法限制
实际开发中发现的问题及解决方案:
- 移动端输入框体验问题:改用触摸友好的数字选择器
- 高并发下库存不一致:引入Redis原子计数器
- 批量操作超时问题:分页处理大数据量
后续优化方向:
- 持久化未登录用户的购物车
- 加入商品价格变动提醒
- 实现购物车商品推荐功能