简介:这是一套基于Django框架的Python全栈项目源码,以手机商城为主题,适合正在学习Web全栈开发、需要完整项目练手或课程设计参考的开发者。项目采用前后端分离思路:前端为微信小程序,主要负责商品展示与数据呈现;后端基于Django搭建管理后台,完成数据维护与接口支撑,覆盖从模型设计到页面渲染的完整链路。压缩包共102个文件,约497KB,包含16个py后端逻辑文件、13个js脚本、13个json配置、11个wxss与9个wxml小程序页面文件,以及若干png、jpg图片素材和4个html模板,另附sqlite数据库与说明文档,结构紧凑、便于快速运行与二次修改。目前已有154人学习下载。通过该资源可掌握Django项目目录组织、小程序页面与后端数据对接方式,并借鉴商城类项目的模块划分与接口设计思路,适合作为全栈入门到进阶的实战参考。
1. 手机商城全栈项目:为什么 Django 依然是快速交付的稳妥选择
做过几个电商类外包的同行大概都有体会,客户要的从来不是「最先进的技术栈」,而是「下个月能不能上线、后台能不能改价、订单别丢」。手机商城这类项目尤其典型:SKU 有颜色和容量两个维度、下单要锁库存、支付要回调对账、后台运营要能自己改 banner。这些需求拆开看都不难,难的是把它们在两周内拼成一个能跑、能交付、能维护的整体。Django 在这个场景里的价值,不是它有多快,而是它把用户体系、Admin 后台、ORM、表单校验、CSRF 防护这些电商必备件全部内置了,你少写的每一行胶水代码,都是后期少踩的一个坑。这篇笔记就按「手机商城」这个具体品类,把从建表到下单落库的完整链路讲一遍,适合能写 Python、想接一个完整全栈项目练手或交付的开发者。
2. 数据建模:手机商城的 SKU 为什么不能只有一张商品表
手机商城和服装、食品最大的区别在于规格组合是结构化的:一台机器固定有颜色和存储容量两个销售属性,组合出来的就是 SKU。很多新手第一版会直接在商品表上加color和storage两个字段,结果同一款手机要插好几行,改一次价格要 update 多条记录,库存还对不上。正确做法是把「商品」和「SKU」拆成两层,商品层放公共信息(品牌、型号、详情图),SKU 层放可售单元(颜色+容量+价格+库存)。
2.1 商品、SKU、分类三张核心表的字段设计
先看模型定义,这里用 Django 的 ORM 写,注意related_name和索引的设置,后面查列表页会用到。
# shop/models.py from django.db import models class Category(models.Model): name = models.CharField(max_length=50, unique=True) sort = models.IntegerField(default=0) # 后台排序用,越小越靠前 class Meta: ordering = ['sort'] class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name='products') name = models.CharField(max_length=120, db_index=True) brand = models.CharField(max_length=50) cover = models.ImageField(upload_to='product/cover/') detail = models.TextField(blank=True) is_on_sale = models.BooleanField(default=True) # 下架不删数据 created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [models.Index(fields=['category', 'is_on_sale'])] class SKU(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='skus') color = models.CharField(max_length=20) storage = models.CharField(max_length=20) # 用字符串存 "128GB",避免单位混乱 price = models.DecimalField(max_digits=10, decimal_places=2) stock = models.IntegerField(default=0) sku_code = models.CharField(max_length=40, unique=True) # 对接仓储用 class Meta: unique_together = ('product', 'color', 'storage') # 防重复组合逻辑说明:Category用PROTECT而不是CASCADE,是因为分类被误删会连带清空商品,运营事故里这类问题排前三。SKU上的unique_together是硬约束,防止后台运营重复录入「黑色 128G」。sku_code单独留一个字段,是为了将来对接第三方仓储或 ERP 时不用改主键。
参数说明:price必须用DecimalField,用FloatField会在满减、优惠券叠加时出现0.1+0.2这类精度问题,对账时非常难查。stock用IntegerField而不是PositiveIntegerField,是因为部分场景需要允许负库存占位(比如预售),具体看业务,但字段类型先留余地。
2.2 迁移命令与后台注册的最小闭环
模型写完先跑迁移,再注册到 Admin,这样运营当天就能录数据。
python manage.py makemigrations shop python manage.py migrate python manage.py createsuperuser# shop/admin.py from django.contrib import admin from .models import Category, Product, SKU class SKUInline(admin.TabularInline): # 在商品页直接编辑 SKU model = SKU extra = 1 @admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display = ('name', 'brand', 'category', 'is_on_sale') list_filter = ('category', 'is_on_sale') inlines = [SKUInline] admin.site.register(Category)逻辑说明:TabularInline让运营在编辑商品时直接增删 SKU,不用来回跳页面,这是手机商城后台使用频率最高的操作。list_filter加上is_on_sale,是为了让运营快速筛出下架商品做批量处理。
参数说明:extra = 1表示默认显示一行空 SKU 表单,设太大页面会卡,设 0 则运营找不到新增入口,1 是实测比较顺手的值。
3. 购物车与下单:库存扣减到底该在哪一步做
购物车和下单是手机商城最容易出 bug 的两块。购物车可以放 Session,也可以放数据库,取决于要不要跨设备同步;下单则必须解决「两个人同时买最后一台」的超卖问题。这一章把两条链路都走一遍。
3.1 购物车用 Session 还是数据库,看这三个判断点
判断标准很直接:要不要登录后才能加购、要不要跨设备同步、要不要做弃购召回。手机商城通常三个都要,所以用数据库表更稳。
# cart/models.py from django.db import models from django.contrib.auth.models import User from shop.models import SKU class CartItem(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='cart_items') sku = models.ForeignKey(SKU, on_delete=models.CASCADE) quantity = models.IntegerField(default=1) added_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'sku') # 同一 SKU 只累加数量逻辑说明:unique_together保证同一用户对同一 SKU 只有一条记录,加购时用update_or_create累加数量,避免购物车出现两行相同 SKU。
参数说明:quantity在加购接口里要做上限校验,常见做法是单 SKU 不超过 200,防止有人写脚本刷库存占用。
3.2 下单接口:事务、行锁与库存扣减的先后顺序
下单的核心是把「校验库存 → 扣库存 → 生成订单 → 清购物车」放进一个事务,并且对 SKU 行加锁。
# order/views.py from django.db import transaction from django.shortcuts import get_object_or_404 from rest_framework.decorators import api_view from rest_framework.response import Response from shop.models import SKU from .models import Order, OrderItem @api_view(['POST']) def create_order(request): sku_id = request.data.get('sku_id') qty = int(request.data.get('quantity', 1)) with transaction.atomic(): # select_for_update 对 SKU 行加锁,阻塞并发下单 sku = SKU.objects.select_for_update().get(id=sku_id) if sku.stock < qty: return Response({'code': 4001, 'msg': '库存不足'}, status=400) sku.stock -= qty sku.save(update_fields=['stock']) order = Order.objects.create(user=request.user, total=sku.price * qty) OrderItem.objects.create(order=order, sku=sku, price=sku.price, quantity=qty) return Response({'code': 0, 'order_id': order.id})逻辑说明:select_for_update()必须在事务内使用,它会对查询到的 SKU 行加排他锁,第二个并发请求会阻塞到第一个事务提交,从而避免超卖。注意sku.save(update_fields=['stock'])只更新库存字段,减少写放大。
参数说明:transaction.atomic()的粒度要控制好,不要把发短信、写日志这类外部调用放进去,否则事务持有时间过长,锁等待会拖垮下单接口。库存判断用<而不是<=,是因为stock == qty时应该允许下单。
提示:如果用的是 MySQL,
select_for_update需要 InnoDB 引擎且关闭自动提交;SQLite 在开发环境会退化成整库锁,压测数据不可信,上线前务必在目标数据库上验证。
4. 避坑与排查:手机商城项目里最容易翻车的五个点
这一章全是血泪经验,每条都按「现象 → 原因 → 解决」写,遇到对应症状直接对号入座。
4.1 图片上传后前台 404
现象:Admin 后台上传了商品封面,前台<img>标签打开是裂图,控制台报 404。原因:开发环境没有配置MEDIA_URL和MEDIA_ROOT的路由,Django 默认不服务媒体文件。解决:在settings.py里加MEDIA_URL = '/media/'和MEDIA_ROOT = BASE_DIR / 'media',并在urls.py里追加static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)。注意这段只在DEBUG=True时生效,生产环境要交给 Nginx 或对象存储。
4.2 下单接口偶发超卖
现象:压测时库存 10 件卖出 12 单。原因:库存判断和扣减之间没有加锁,两个请求同时读到stock=1都判断通过。解决:按 3.2 的方式用select_for_update包住,或者用数据库层面的UPDATE ... SET stock = stock - 1 WHERE stock >= 1原子更新,判断受影响行数是否为 1。
4.3 价格字段出现 0.30000000000000004
现象:订单金额算出来带一长串小数。原因:用了FloatField或 Python 原生float做金额运算。解决:模型层统一DecimalField,运算时用Decimal对象,序列化输出时用str()或 DRF 的DecimalField,不要中途转 float。
4.4 后台删除分类导致商品全部消失
现象:运营删了一个分类,该分类下商品全部查不到。原因:外键用了CASCADE。解决:改成PROTECT,删除时抛ProtectedError,让运营先处理商品再删分类。如果业务确实要删,做软删除,加is_deleted字段。
4.5 列表页 N+1 查询拖慢接口
现象:商品列表页每行都要查一次分类名和最低价,20 条数据发了 40 多条 SQL。原因:模板或序列化器里直接访问product.category.name和product.skus.first()。解决:查询时用select_related('category')和prefetch_related('skus'),把关联数据一次拉回来。用 Django Debug Toolbar 看 SQL 条数,超过 5 条就要警惕。
5. 从能跑到能交付:三个让手机商城项目更值钱的进阶技巧
项目能跑起来只是及格线,能不能交付、能不能被接手,看的是后面这些细节。第一个技巧是给订单状态机加约束。很多新手用status字段存 0/1/2,但没有任何校验,结果出现「已取消」的订单又被改成「已发货」。常见做法是定义一个状态流转字典,在save()或 service 层校验。
# order/models.py ORDER_FLOW = { 'pending': ['paid', 'canceled'], 'paid': ['shipped', 'refunded'], 'shipped': ['done'], 'done': [], 'canceled': [], } class Order(models.Model): STATUS_CHOICES = [(k, k) for k in ORDER_FLOW] status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') def can_transit(self, target): return target in ORDER_FLOW.get(self.status, [])逻辑说明:ORDER_FLOW把合法流转集中在一处,改业务规则只动这一个字典。can_transit在视图层调用,不合法就返回 400,避免脏状态写库。
第二个技巧是给 SKU 价格加历史表。手机降价频繁,订单里必须记录「下单时的价格」,而不是关联 SKU 现价。OrderItem里冗余一个price字段就是干这个的,别图省事用sku.price反查,否则改价后历史订单金额全变。
第三个技巧是用manage.py check --deploy做上线前自检。这条命令会检查DEBUG、SECRET_KEY、ALLOWED_HOSTS、Cookie 安全属性等十几项配置,输出里带WARNINGS的逐条处理。我一般会在 CI 里加一步,配置有问题直接卡住构建,比上线后被打回强。
| 检查项 | 开发环境 | 生产环境 |
|---|---|---|
| DEBUG | True | False |
| SECRET_KEY | 硬编码 | 环境变量读取 |
| ALLOWED_HOSTS | 空 | 填实际域名 |
| 数据库 | SQLite | PostgreSQL/MySQL |
| 静态文件 | runserver 托管 | Nginx/对象存储 |
最后说个习惯:每次改完模型,先makemigrations看生成的迁移文件再migrate,别直接一把梭。我踩过一次坑,字段改名时 Django 默认生成的是「删旧列+加新列」,数据全丢,正确做法是在迁移里用RenameField。这个后悔药没有,只能靠看迁移文件提前发现。希望帮到你。
本文还有配套的精品资源,点击获取