
简介本资源是一套基于Django框架实现的完整电商项目——小鱼商城源码面向Python Web开发初学者与中级开发者聚焦电商系统核心功能实践助力掌握前后端协同开发、模块化架构设计及Django企业级应用落地。压缩包共447个文件总大小19.03MB涵盖43个Python源文件含users、carts、orders等清晰分层模块、20个JavaScript交互脚本、3个CSS样式表、288个JPG与26个PNG商品及界面图片以及XML配置、HTML模板和开发环境配置文件如.iml体现典型MVT模式下的工程组织逻辑。已有608人学习下载可直接运行调试深入理解用户认证、购物车状态管理、订单流程控制等关键业务实现并参考.gitignore、README等辅助文件快速搭建本地开发环境。1. 小鱼商城不是模板套壳而是用 Django 实现真实电商闭环的可运行源码项目“小鱼商城”这个名字在 Python 开发者圈里常被当作 Django 入门项目的代称但多数人只见过带 admin 后台的空壳 demo——商品列表能渲染、购物车能加减、订单能提交却卡在支付回调验签失败、库存扣减并发超卖、用户地址联动省市区三级选择、商品 SKU 多规格组合渲染这些真实场景上。本项目源码解决的正是这类“能跑通但不敢上线”的断层它基于 Django 4.2兼容 Python 3.10–3.12完整实现从用户注册登录、商品搜索与多条件筛选品牌/价格区间/销量排序、购物车合并与跨设备同步、下单时实时校验库存与优惠券可用性、到订单状态机驱动待支付→已支付→发货→已完成→已关闭的全链路。适合两类人一是刚学完 Django MTV 模式、想用一个结构清晰又不脱离生产逻辑的项目练手的开发者二是需要快速验证某模块如 Django Signals 处理订单创建后发短信、或 django-crispy-forms 渲染复杂收货表单落地效果的中级工程师。它不追求炫酷前端但所有接口均提供 RESTful 风格设计为后续接入 Vue/React 前端预留明确契约。2. 用 Django 构建小鱼商城核心模型从 E-R 关系到迁移命令的完整推演电商系统本质是数据关系的精密编排。小鱼商城源码中模型设计严格遵循业务视角下的商品模块最佳实践而非简单堆砌字段。其核心并非“User Product Order”三张表而是通过精准的外键约束与反向关系让查询自然贴近业务语义。例如用户地址不直接挂在 User 表下而是独立 Address 模型并通过is_default字段与user外键构成一对多商品不只存主图而是通过ProductImage模型关联支持同一商品多角度图片轮播最关键的是 SKU库存量单位与 SPUI销售属性单位分离ProductSKU模型承载具体可售实体含 price、stock、code而ProductSPU描述抽象商品如“iPhone 15 Pro”含 brand、category、description二者通过spu models.ForeignKey(ProductSPU, on_deletemodels.CASCADE)连接。这种设计使后台可批量管理 SPU 属性前台按 SKU 组合渲染规格选择器。2.1 模型定义与外键策略详解以OrderInfo和OrderGoods为例这是电商订单拆单的关键结构# apps/orders/models.py class OrderInfo(models.Model): ORDER_STATUS_CHOICES ( (unpaid, 待支付), (paid, 已支付), (shipped, 已发货), (finished, 已完成), (closed, 已关闭), ) order_id models.CharField(max_length64, primary_keyTrue, verbose_name订单号) user models.ForeignKey(users.UserProfile, on_deletemodels.PROTECT, verbose_name用户) address models.ForeignKey(users.Address, on_deletemodels.PROTECT, verbose_name收货地址) total_amount models.DecimalField(max_digits10, decimal_places2, verbose_name订单总金额) status models.CharField(max_length20, choicesORDER_STATUS_CHOICES, defaultunpaid, verbose_name订单状态) class OrderGoods(models.Model): order models.ForeignKey(OrderInfo, on_deletemodels.CASCADE, related_namegoods, verbose_name所属订单) sku models.ForeignKey(goods.ProductSKU, on_deletemodels.PROTECT, verbose_name商品SKU) count models.IntegerField(verbose_name购买数量) price models.DecimalField(max_digits10, decimal_places2, verbose_name单价)注意OrderInfo.user使用on_deletemodels.PROTECT而非CASCADE防止误删用户导致订单数据丢失OrderGoods.order则用CASCADE确保订单删除时自动清理明细符合事务一致性。related_namegoods让查询更自然order.goods.all()即可获取该订单全部商品项。2.2 生成并执行迁移文件从模型到数据库表的精确控制模型写完后必须生成迁移脚本并应用。小鱼商城采用分步迁移策略避免单个大迁移文件难以回滚# 1. 在 apps/goods 目录下生成商品相关迁移仅影响 goods app python manage.py makemigrations goods --name create_product_models # 2. 查看将要执行的 SQL关键确认无 DROP TABLE 或意外字段修改 python manage.py sqlmigrate goods 0001_create_product_models # 3. 执行迁移生产环境务必先备份 python manage.py migrate goods # 4. 为 orders app 单独生成迁移解耦依赖 python manage.py makemigrations orders --name add_order_relations python manage.py migrate orders2.2.1 迁移过程中的典型陷阱与绕过方案陷阱makemigrations提示 No changes detected 但模型已改原因Django 仅比对models.py与migrations/下的.py文件若手动编辑了迁移文件如删掉某字段的AddField操作会导致状态错乱。解法运行python manage.py showmigrations查看未应用的迁移再用python manage.py migrate --fake-initial强制标记初始迁移为已应用仅限新库首次初始化。陷阱ForeignKey引用未定义的 app如goods.ProductSKU中goodsapp 尚未makemigrationsDjango 会报LookupError: No installed app with label goods。解法确保 apps 注册顺序正确INSTALLED_APPS中goods必须在orders之前且所有 app 的apps.py已正确声明default_app_config。3. 小鱼商城前后端交互Django 视图层设计与 REST 接口实现小鱼商城源码采用“Django View 原生 HTML 模板”作为默认交付形态但所有核心业务逻辑均封装为可复用的函数视图Function-Based Views天然支持向 REST API 迁移。其接口设计严格遵循电商页面实现的高频需求商品列表需支持分页与多条件过滤购物车操作需处理登录态与匿名用户 ID 绑定订单创建需原子化扣减库存并生成流水。关键不在“能不能做”而在“怎么做才不踩坑”。3.1 商品列表视图分页、搜索与缓存的三层协同goods/views.py中的GoodsListView是典型电商页面入口它不依赖第三方包仅用 Django 内置工具实现高性能响应# apps/goods/views.py from django.core.paginator import Paginator from django.db.models import Q from django.views.generic import ListView from django.core.cache import cache class GoodsListView(ListView): model ProductSKU template_name goods/list.html context_object_name goods_list paginate_by 20 # 每页20条兼顾加载速度与用户体验 def get_queryset(self): # 1. 获取查询参数 category_id self.request.GET.get(category_id) keyword self.request.GET.get(q, ).strip() price_min self.request.GET.get(price_min) price_max self.request.GET.get(price_max) # 2. 构建动态查询集Q 对象组合 queryset ProductSKU.objects.filter(is_on_saleTrue) if category_id: queryset queryset.filter(spu__category_idcategory_id) if keyword: queryset queryset.filter(Q(spu__name__icontainskeyword) | Q(sku_code__icontainskeyword)) if price_min and price_max: queryset queryset.filter(price__range(float(price_min), float(price_max))) # 3. 添加 Redis 缓存key 包含所有查询参数哈希值 cache_key fgoods_list_{hash(f{category_id}_{keyword}_{price_min}_{price_max})} cached_data cache.get(cache_key) if cached_data is not None: return cached_data # 缓存未命中则执行查询并设置缓存30分钟 result list(queryset.select_related(spu).prefetch_related(images)[:1000]) cache.set(cache_key, result, 60 * 30) return result逻辑说明select_related(spu)解决 N1 查询问题一次 JOIN 获取 SPU 信息prefetch_related(images)预加载图片避免循环查表。cache_key使用hash()生成简短唯一标识避免 key 过长[:1000]限制最大结果数防止恶意构造参数拖垮数据库。3.2 购物车视图处理登录与未登录用户的统一存储方案小鱼商城购物车不依赖 Session 存储全部数据易爆内存而是采用“登录用户存数据库 未登录用户存 Cookie”的混合模式。cart/views.py中的CartAddView是核心# apps/cart/views.py import json from django.http import JsonResponse from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator method_decorator(csrf_exempt, namedispatch) class CartAddView(View): def post(self, request): # 1. 解析请求体JSON 格式 try: data json.loads(request.body.decode(utf-8)) sku_id int(data.get(sku_id)) count int(data.get(count, 1)) except (json.JSONDecodeError, ValueError, TypeError): return JsonResponse({status: fail, msg: 参数错误}, status400) # 2. 判断用户登录状态 if request.user.is_authenticated: # 登录用户写入数据库 cart_item 表 cart_item, created CartItem.objects.get_or_create( userrequest.user, sku_idsku_id, defaults{count: count} ) if not created: cart_item.count count cart_item.save() # 返回当前用户购物车总数量用于顶部徽标 total_count CartItem.objects.filter(userrequest.user).aggregate(totalSum(count))[total] or 0 else: # 未登录用户写入加密 Cookie有效期7天 cart_str request.COOKIES.get(cart, {}) try: cart_dict json.loads(cart_str) except json.JSONDecodeError: cart_dict {} cart_dict[str(sku_id)] cart_dict.get(str(sku_id), 0) count total_count sum(cart_dict.values()) # 3. 设置响应 Cookie 并返回 response JsonResponse({status: success, total_count: total_count}) if not request.user.is_authenticated: response.set_cookie(cart, json.dumps(cart_dict), max_age60*60*24*7, httponlyTrue) return response参数说明httponlyTrue防止 XSS 窃取购物车数据max_age60*60*24*7设为 7 天平衡用户体验与 Cookie 大小get_or_create保证并发添加时不会重复插入记录。4. 小鱼商城部署与性能调优宝塔面板下 Django MySQL 的稳定运行配置将小鱼商城从本地开发环境迁移到生产服务器最常见路径是使用宝塔面板Linux Nginx uWSGI MySQL。但直接套用宝塔一键部署 Django 功能极易因静态文件路径、数据库连接池、uWSGI 进程数等配置不当导致 502 错误或高延迟。本节给出经实测验证的最小可行配置覆盖从环境初始化到压测调优的完整链路。4.1 宝塔环境下 Django 项目部署四步法第一步创建纯净 Python 环境在宝塔「软件商店」安装 Python 项目管理器推荐 3.11 版本新建项目时勾选「创建虚拟环境」路径设为/www/wwwroot/xiaoyu-shop/venv。切勿使用系统 Python避免包冲突。第二步配置 uWSGI 启动文件在项目根目录创建uwsgi.ini关键参数如下[uwsgi] chdir /www/wwwroot/xiaoyu-shop module xiaoyu_shop.wsgi:application home /www/wwwroot/xiaoyu-shop/venv master true processes 4 threads 2 socket /www/wwwroot/xiaoyu-shop/xiaoyu.sock chmod-socket 664 vacuum true die-on-term true logto /www/wwwroot/xiaoyu-shop/logs/uwsgi.log参数说明processes 4适配 2核4G 服务器vacuum true退出时自动清理 socket 文件logto指定日志路径便于排查启动失败原因如ImportError: No module named django即虚拟环境未激活。第三步Nginx 反向代理配置在宝塔网站设置中「配置文件」追加以下内容替换原 location 块location / { include uwsgi_params; uwsgi_pass unix:/www/wwwroot/xiaoyu-shop/xiaoyu.sock; uwsgi_read_timeout 300; uwsgi_send_timeout 300; } location /static/ { alias /www/wwwroot/xiaoyu-shop/staticfiles/; expires 1y; add_header Cache-Control public, immutable; } location /media/ { alias /www/wwwroot/xiaoyu-shop/media/; expires 1y; }注意uwsgi_read_timeout必须大于 300 秒否则支付回调如微信异步通知可能被 Nginx 中断/static/和/media/的alias路径必须与 Djangosettings.py中STATIC_ROOT和MEDIA_ROOT一致。第四步MySQL 连接池与慢查询优化在settings.py中启用连接池避免频繁创建连接# DATABASES 配置追加 OPTIONS DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: xiaoyu_db, USER: xiaoyu_user, PASSWORD: your_secure_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, charset: utf8mb4, read_timeout: 10, write_timeout: 10, connect_timeout: 10, }, CONN_MAX_AGE: 60, # 连接复用60秒 } }同时在宝塔 MySQL 管理中开启慢查询日志阈值设为 1 秒定期分析slow.log对OrderInfo表的status字段和created_time字段添加联合索引-- 在 MySQL 命令行执行 ALTER TABLE orders_orderinfo ADD INDEX idx_status_created (status, created_time);4.2 压测验证用 ab 工具检测首页并发能力部署完成后必须验证基础性能。使用 Apache Benchab模拟真实用户访问# 测试首页GET /在 100 并发下的表现 ab -n 1000 -c 100 http://xiaoyu-shop.com/ # 关键指标解读 # Time per request: 123.456 [ms] (mean) → 单请求平均耗时 200ms 合格 # Failed requests: 0 → 0 失败为佳 # Transfer rate: 1234.56 [Kbytes/sec] → 带宽占用合理若Time per request超过 500ms优先检查① Nginx 是否启用了gzip on;② DjangoDEBUGFalse是否生效DEBUGTrue 会极大拖慢③ MySQL 查询是否命中索引用EXPLAIN分析SELECT * FROM goods_productsku WHERE is_on_sale1 LIMIT 20。5. 小鱼商城订单状态机与库存扣减用 Django Signals 和数据库事务保障数据强一致电商系统最脆弱的环节是订单创建与库存扣减的原子性。小鱼商城源码采用“数据库事务 Django Signals”双保险机制订单保存在事务内完成同时触发post_save信号异步更新库存、发送通知、记录日志。这既保证核心数据不超卖又避免阻塞主流程。5.1 订单创建事务显式控制库存扣减边界orders/views.py中的OrderCommitView是下单入口其核心逻辑包裹在transaction.atomic()中# apps/orders/views.py from django.db import transaction from django.db.models import F class OrderCommitView(View): transaction.atomic def post(self, request): # 1. 获取购物车数据登录用户查 DB未登录用户解析 Cookie cart_items self._get_cart_items(request) if not cart_items: return JsonResponse({status: fail, msg: 购物车为空}) # 2. 创建订单主表此时 statusunpaid order OrderInfo.objects.create( order_idf{int(time.time())}{request.user.id:06d}, userrequest.user, addressAddress.objects.get(idrequest.POST.get(address_id)), total_amountself._calc_total(cart_items), statusunpaid ) # 3. 批量创建订单明细并扣减库存关键 order_goods_list [] for item in cart_items: # 使用 F() 表达式原子扣减避免竞态条件 sku ProductSKU.objects.select_for_update().get(iditem[sku_id]) if sku.stock item[count]: raise ValueError(fSKU {sku.sku_code} 库存不足剩余{sku.stock}) sku.stock F(stock) - item[count] sku.sales F(sales) item[count] sku.save() order_goods_list.append( OrderGoods(orderorder, skusku, countitem[count], pricesku.price) ) # 4. 批量插入订单明细 OrderGoods.objects.bulk_create(order_goods_list) # 5. 清空购物车登录用户 if request.user.is_authenticated: CartItem.objects.filter(userrequest.user).delete() return JsonResponse({status: success, order_id: order.order_id})逻辑说明select_for_update()对 SKU 记录加行锁确保并发请求不会读到旧库存值F(stock) - item[count]是数据库层面的原子运算无需先查后改bulk_create减少 SQL 执行次数提升性能。5.2 用 Django Signals 实现订单状态变更的自动响应订单状态流转如支付成功后status从unpaid变为paid需触发下游动作更新商品销量、通知物流系统、发送站内信。小鱼商城在orders/signals.py中定义信号处理器# apps/orders/signals.py from django.db.models.signals import post_save from django.dispatch import receiver from .models import OrderInfo receiver(post_save, senderOrderInfo) def update_order_status(sender, instance, created, **kwargs): # 仅处理 status 字段变更且非新建订单 if created or not kwargs.get(update_fields): return if status not in kwargs[update_fields]: return # 支付成功后异步更新商品销量避免阻塞 if instance.status paid: from celery import current_app current_app.send_task(orders.tasks.update_sku_sales, args[instance.id]) # apps/orders/tasks.py 需配置 Celery from celery import shared_task shared_task def update_sku_sales(order_id): try: order OrderInfo.objects.get(idorder_id) for item in order.goods.all(): # 更新 SKU 销量同样用 F 表达式 item.sku.sales F(sales) item.count item.sku.save(update_fields[sales]) except OrderInfo.DoesNotExist: pass提示Celery 任务需单独部署 worker 进程宝塔中可通过「计划任务」添加守护进程命令为celery -A xiaoyu_shop worker -l info。若暂不引入 Celery可改用 Django Q 任务队列但需注意数据库连接池配置。5.3 验证库存扣减正确性的三个必查点上线前必须人工验证库存逻辑是否坚如磐石检查点验证方法预期结果并发超卖用locust启动 50 用户同时抢购同一 SKU库存1最终ProductSKU.stock 0且仅生成 1 笔有效订单回滚一致性在订单创建事务中手动抛出异常如raise Exception(test rollback)订单主表、明细表、SKU 库存均无任何变更状态机完整性手动 SQL 修改OrderInfo.status为shipped触发post_save信号物流通知任务应被调度查 Celery 日志执行上述验证后小鱼商城的订单核心链路即具备生产就绪条件。真正的电商稳定性不在于代码行数而在于每一处select_for_update()的位置、每一个transaction.atomic()的包裹范围、以及每一次F()表达式的精准使用。本文还有配套的精品资源点击获取