我的一个前同事去年做了一款“基于Python的米家商城”作为毕业设计,答辩结束后主动把整套源码梳理了一遍发给我,说“这玩意比想象的坑多,数据结构再磨一磨就真能上线了”。我看完整个项目之后相当感慨——它麻雀虽小,却把电商系统的核心链路完整走了一遍,从用户注册登录、商品展示、购物车再到订单结算、后台管理,一应俱全。做毕设、课程设计或者想快速理解Python Web开发完整流程的同学,拿它练手再合适不过,关键是可以直接复现,不需要额外花钱买服务器或支付接口。
这篇文章我打算把这个项目从设计思路、数据库建模、功能实现到部署运行的每个环节都拆开讲一遍,顺便把我实际跑源码时踩到的坑和修复方式一并记录下来。不管你是准备拿它当毕业设计,还是只是好奇一个商城系统后端的代码长什么样,这篇都能给你省下不少折腾时间。
1. 项目整体拆解:一个“迷你小米商城”到底包含哪些东西
1.1 毕设需求梳理
先别急着看代码,拿到任何项目源码,第一步都得先搞清楚它的需求边界。米家商城这个项目本质是一个“迷你B2C商城”,它的目标不是做出京东、淘宝那种复杂度,而是把电商后端最核心、最能体现开发者水平的模块做完整。按我拆解的模块来看,它主要覆盖了以下几条主线:
- 前台用户端:注册、登录、退出、商品浏览、商品详情、加入购物车、购物车管理、下单结算、个人订单查询。
- 后台管理端:管理员登录、商品分类管理、商品信息维护、订单状态管理、用户列表查看。
- 基础支撑:数据库表设计、静态资源处理、分页、搜索、系统配置等。
这个范围放在毕业设计里属于“中等偏上”的水平。如果只做简单的CRUD,评委一眼就能看穿,肯定要追问业务逻辑。而这个项目把“购物车”和“订单”两个业务场景做出了完整闭环,尤其是订单状态变化和库存扣减的联动逻辑,比单纯增删改查高出一个段位。
1.2 技术选型:Django 还是 Flask
开题时最纠结的往往是框架选型。标题只写了“基于Python”,没指定框架,但我打开源码一眼就认出来用的是Django。为什么?因为商城这种业务系统,最看重的是“开发效率 + 内置功能完整度”,Django自带的ORM、Admin后台、表单处理、认证系统,几乎是为这类系统量身定制的。
对比来看:
| 对比项 | Django | Flask |
|---|---|---|
| 上手门槛 | 偏高,概念多 | 低,轻量灵活 |
| ORM | 内置,功能强大 | 需自行集成SQLAlchemy |
| 后台管理 | 自带Admin,开箱即用 | 需要自己写或装第三方库 |
| 认证系统 | 内置User模型和会话 | 需要拓展插件 |
| 适合场景 | 业务完整的中小型系统 | 轻量API、微服务、单页应用 |
做毕设,时间是硬约束。用Flask写商城,光是用户认证、分页、表单校验这些基础功能就要自己铺不少代码,而Django把这些都省了,你可以把精力放在核心业务逻辑上。所以项目选Django不是偶然,是所有相似毕设里的最优解。
另外,Django的MTV架构和面试时经常问的MVC思路高度对应,答辩讲解时也更容易把条理说清楚——评委听到“模型层、视图层、模板层”这几个词就知道你是真理解了框架,而不是只会照着文档敲代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据模型设计:把商城的“架子”先立起来
2.1 用户、商品、订单三个核心实体
电商系统无论多复杂,归根到底逃不出三个核心概念:用户、商品、订单。米家商城的设计也遵循这个路径,但它比较聪明的地方在于利用了Django内置的User模型做基础,再通过OneToOneField扩展出一个用户扩展表,这样既省代码又符合真实的用户体系演进规律。
核心数据表有这几张:
UserProfile:用户扩展表,存手机号、收货地址、头像等字段。Category:商品分类表,存分类名称和父级分类。Product:商品表,存标题、图片、价格、库存、销量、描述、上下架状态。CartItem:购物车条目,关联用户和商品,记录加入数量。Order:订单主表,记录订单编号、用户、总金额、状态、创建时间。OrderItem:订单明细表,记录某个订单里每个商品的快照信息。
特别注意OrderItem的设计,很多新手会在这里偷懒,直接关联Product表,但这样后面一旦改了商品价格或者删了商品,历史订单就乱了。这个项目在订单明细里保存了商品名称和下单时的价格快照,这个细节在答辩时值得主动提一句,能加不少印象分。
2.2 数据库关系与关键字段
讲几个容易踩坑的关键字段设计:
第一,商品价格字段必须用DecimalField而不是FloatField。浮点数在Python内部是二进制存储,算钱的时候会出现0.1 + 0.2 != 0.3的问题。Django的DecimalField底层用十进制运算,虽然性能略有损耗,但财务数据准确性永远优先。
python复制class Product(models.Model):
name = models.CharField(max_length=200, verbose_name='商品名称')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='价格')
stock = models.IntegerField(default=0, verbose_name='库存')
image = models.ImageField(upload_to='products/', blank=True, verbose_name='商品图片')
第二,库存字段必须加default=0,并且在下单流程里要配合事务处理。我看到有一些偷懒的写法是直接对stock做减法,没有判断库存够不够,这样写出来的系统超卖以后数据全是负数。这个问题我在第三部分详细讲。
第三,订单编号不要用自增ID直接展示给用户。一是因为业务上订单号需要唯一且无规律,二是因为自增ID会被别人猜出订单量,泄露商业数据。项目里生成了类似20250518123000123456的订单号,用的是时间戳加随机数的组合方案。
另外,外键关系的on_delete参数值得好好看一遍。项目里凡是“订单关联商品”这种需要保留历史记录的,都用SET_NULL加null=True;凡是“购物车条目关联用户”这种用户删了条目也要删的,就用CASCADE。用错这个参数,后面删数据时会要么报错要么留下一堆孤儿数据。
3. 核心功能实现与踩坑实录
3.1 用户认证:别重复造轮子
我看过太多毕设源码,用户认证这块自己手写了一整套session逻辑,结果漏洞百出。这个项目的正确示范是:直接用Django自带的auth模块。
python复制from django.contrib.auth import authenticate, login, logout
def user_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('home')
else:
return render(request, 'login.html', {'error': '用户名或密码错误'})
return render(request, 'login.html')
这段代码看起来简单,但背后Django帮你处理了密码哈希、Session管理、CSRF防护等一系列安全问题。这里我要强调一句:毕设也至少要保证基本安全,不能明文存密码。Django默认的PBKDF2算法已经比MD5高好几个层级,学会用框架的认证系统本身就是一种能力体现。
真正需要自己扩展的是“注册即登录”这个体验优化。正常流程是在注册成功后自动调一次login(),把用户状态直接拉起来,不用让他再跳回登录页输一遍账号密码。项目源码里这一步做得比较完整,还做了表单校验:用户名唯一性、密码长度、两次密码一致性。
3.2 商品列表与详情页:查询优化是关键
商品浏览是商城流量入口,这里优化得好不好,直接影响用户体感。最基础的翻页功能,项目用的是Django自带的分页器Paginator,这个没什么坑。但商品列表页的商品图片处理值得讲一讲。
在开发环境下,ImageField上传的图片默认存在media/目录,需要先在settings.py里配置:
python复制MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
然后在项目的urls.py里添加一个静态映射,否则图片会404:
python复制from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# 其他路由
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
这一步是新手最容易踩的坑,因为Django官方文档里明确写了“生产环境不要用django serve media”,很多人就直接把这段代码删了,结果开发环境也访问不到图片。
商品详情页的亮点是“推荐商品”逻辑,它根据当前商品的分类ID,查询同分类下其他商品,排除当前自身,再随机取出4条。这个随机用的是order_by('?'),数据量小的时候没问题,但数据量大了会有性能瓶颈。毕设级别可以忽略,但如果想在答辩时展示点思考深度,可以把它替换成“同分类销量Top4”或“浏览记录关联推荐”。
3.3 购物车逻辑:用Session还是用数据库
购物车是商城系统里最能拉开代码水平差距的模块。这个项目的设计是在数据库中持久化购物车数据,也就是建了CartItem表,关联用户ID和商品ID。优点是用户换设备或者清浏览器缓存后购物车数据不丢,缺点是需要处理登录态和匿名用户的差异。
项目里的处理方式是:只有登录用户才能把商品加入购物车。思路简单直接,不用考虑匿名购物车迁移到登录账号的复杂场景。这个取舍在毕设里完全说得通,因为核心要展示的是“购物车CRUD + 金额计算”的逻辑,而不是“多终端购物车同步”这种遗留系统才需要的能力。
购物车页面的核心逻辑是数量修改和总价计算。数量修改用了一个表单POST请求,提交商品ID和新数量,后端先判断库存是否足够,足够则更新数量,否则返回错误提示。总价计算在前端模板里用Django模板过滤器累加,也在视图函数里算了一份,双保险。
有个细节做得很好:下单时扣减库存用的是select_for_update()加事务,这在并发场景下能防止库存超卖。代码大致长这样:
python复制from django.db import transaction
@transaction.atomic
def create_order(request):
cart_items = CartItem.objects.select_for_update().filter(user=request.user)
for item in cart_items:
if item.product.stock < item.quantity:
return JsonResponse({'code': 1, 'msg': f'商品{item.product.name}库存不足'})
item.product.stock -= item.quantity
item.product.save()
这里select_for_update()会对查询到的行加锁,直到事务提交才释放,别的请求在锁释放前无法修改这些行。概念层面所有商城的库存扣减都需要这个机制,只不过小项目往往忽略掉。能把这个讲到评委听懂,基本就锁定高分了。
3.4 订单流程:状态机思维
订单模块不是简单的“创建一条记录”,它是个状态流转系统。项目里的订单状态包括:待支付、已支付、待发货、已发货、已完成、已取消。状态的流转用了一个字段表示,每次变更时直接赋值新状态。
我在重构这套逻辑时,建议你把它升级成一个更规范的“状态机模型”——把每个状态允许的下一个状态定义清楚,非法流转直接拒绝。比如“已支付”状态不能直接跳到“已完成”,必须经过“已发货”,这是一种最低成本的防呆设计。项目源码里没有做这么严,但作为扩展点在答辩时提一下,效果会非常好。
订单编号生成逻辑当时也让我多看了两眼:
python复制import time
import random
def generate_order_no():
return time.strftime('%Y%m%d%H%M%S') + str(random.randint(1000, 9999))
这个方案在同一秒内并发时理论上有随机冲突概率,但概率极低,做毕设完全够用。真要把订单号做到绝对唯一,可以引入雪花算法,但这属于生产级优化,不用在毕设里卷这一层。
下单时的事务控制是整个项目最核心的部分。它必须保证三件事同时成功或同时失败:创建订单主记录、创建订单明细、扣减库存、清空购物车。任何一步失败都要回滚,否则用户下单不成功但库存没了,或者订单创建了但购物车没清空,都是灾难。项目用transaction.atomic()统一包裹,这正是Django里处理跨表事务的标准姿势。
4. 后台管理端:毕设答辩的关键加分项
4.1 用Django Admin快速搭建管理后台
商城系统最怕什么?最怕你给评委演示商品管理时,说要打开数据库手动INSERT一条记录。本项目用了Django内置Admin,只需要在admin.py里注册模型,就能得到一个可以增删改查商品、订单、用户的管理后台。
我在原代码基础上建议按下面的方式注册,不仅操作方便,展示效果也会好很多:
python复制from django.contrib import admin
from .models import Product, Category, Order, OrderItem
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('name', 'price', 'stock', 'is_active')
list_filter = ('is_active', 'category')
search_fields = ('name', 'description')
这段代码里list_display控制后台列表展示哪些列,list_filter给右侧加上筛选条件,search_fields让管理员可以按关键字模糊搜索商品。这三行配置看起来不起眼,但在后台操作商品时体验差距巨大。
4.2 自定义管理操作:订单状态批量修改
Admin默认只能单条编辑,但实际业务里经常需要“批量发货”这种操作。Django Admin提供了actions机制,可以自定义一个批量操作按钮。
python复制from django.contrib import admin
from .models import Order
def batch_mark_shipped(modeladmin, request, queryset):
queryset.update(status='shipped')
batch_mark_shipped.short_description = "标记为已发货"
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('order_no', 'user', 'total_amount', 'status')
actions = [batch_mark_shipped]
这个扩展点特别适合在答辩时讲“如果商家同时收到几十个订单,如何一键批量处理”的真实痛点。一个action函数几行代码,比在后台挨个点效率高一个数量级,评委听到这种细节基本就知道你不是只学了三个月速成班。
另外,Admin后台的登录用户名密码,需要在运行项目时通过createsuperuser命令创建,这个细节很多同学第一次跑源码时不知道,会在后台登录界面卡住。别问我是怎么知道的。
5. 常见问题与部署心得
5.1 运行环境配置:从源码到能跑的完整步骤
这个项目跑起来的前提是Python环境装好。我建议用虚拟环境隔离依赖,避免和系统Python环境冲突。
bash复制# 克隆或解压源码后,进入项目目录
cd mihome_mall
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境(Windows)
venv\Scripts\activate
# 激活虚拟环境(Linux / macOS)
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt
# 数据库迁移
python manage.py makemigrations
python manage.py migrate
# 创建管理员账号
python manage.py createsuperuser
# 启动开发服务器
python manage.py runserver
依赖安装这一步最容易出问题。如果requirements.txt里没有把依赖写全,运行时会报ModuleNotFoundError。建议在执行migrate之前先跑一遍python manage.py check,这个命令会帮你检查环境配没配好。
5.2 数据库迁移报错的处理
迁移时报错是毕设项目里最常见的翻车现场。我遇到过几种典型情况:
第一,No changes detected。这个提示通常是因为models.py文件改了,但是没有执行makemigrations。注意要带上应用名,比如python manage.py makemigrations mall,如果不带应用名,Django有时候会扫描不到改动。
第二,Table already exists。这是因为你执行migrate之前,数据库里已经有表了,但django_migrations表里没有记录。解决办法是备份数据后删库重建,毕设阶段直接用SQLite文件重来就行。
第三,字段修改后迁移失败。比如改了外键的on_delete,或者给已有数据的表加了一个非空字段但没有给默认值。这种情况Django会进入交互式提示,要求输入默认值或选择退出,跟着提示走就行。
5.3 静态文件和图片404的排查
我刚跑起来这个项目时,商品图片全部裂掉,页面样式也丢了一半。排查思路是这样的:
先在浏览器按F12打开开发者工具,看Network面板里404的资源路径。如果是/static/前缀,检查settings.py里的STATIC_URL和STATICFILES_DIRS;如果是/media/前缀,检查MEDIA_URL和MEDIA_ROOT,再检查urls.py最下面有没有加上static()映射。
如果用的是Django 4.0以上版本,还要注意一个变化:模板里加载静态文件的写法必须是:
html复制{% load static %}
<img src="{% static 'images/logo.png' %}" alt="logo">
不能直接写<img src="/static/images/logo.png">,虽然开发环境可能没报错,但一旦配置了CDN或调整了静态文件收集路径,硬编码的路径就会失效。
5.4 上线部署的取舍
如果只是交作业和答辩,runserver开发服务器完全够用。但有些学校要求部署到云服务器上演示,那就必须换生产级服务器。Django官方的推荐组合是Nginx + Gunicorn + Django,加上SQLite换成MySQL或PostgreSQL。
这个项目部署时的最大改动是静态文件。生产环境不会帮你自动serve静态文件,你需要先执行python manage.py collectstatic,把所有静态文件收集到一个目录,再通过Nginx配置一个location指向这个目录。
有一个部署时很容易被忽略但影响巨大的配置:ALLOWED_HOSTS。如果没有把服务器的域名或IP加进去,会直接报DisallowedHost错误。开发环境下通常配['*'],但上线时必须改成具体的域名列表,这是Django的安全机制。
6. 写在最后:给准备拿这个项目当毕设的人几句交底
我个人实际跑完这套源码之后最大的感受是:它的代码质量已经远超“能跑就行”的及格线,但离“能上生产”还有一段距离,恰到好处地卡在了一个特别适合学习和二次开发的档位上。如果你打算以此为基座做自己的毕设,我劝你不要只满足于换皮改样式,可以从以下几个方向里挑一个深入改造,答辩时直接变成你的差异化亮点。
第一个方向是增加支付模拟模块。现在的订单走到“待支付”状态后没有后续动作,你可以接入支付宝沙箱环境或微信支付沙箱,做一个完整的扫码支付回调,这样订单状态流转就全闭环了。
第二个方向是缓存优化。把商品列表页的热门数据接入Redis,用cache_page装饰器做页面级缓存,再用Redis存储购物车Session,这套改造能让你在“性能优化”主题上站住脚。
第三个方向是搜索增强。现在商品搜索用的是数据库icontains模糊匹配,数据量大时速度会明显下降。换成Elasticsearch或者至少用Django的SearchVector做全文检索,技术含量立刻上升一个档次。
这个项目给我的整体感觉像一个完整度很高的“骨架”,该有的器官都有了,但如果想参加优秀毕设评选,还需要给它注入一些“肌肉”。具体填哪块肌肉,取决于你未来想主攻的方向——后端架构、性能优化还是业务拓展。挑一个方向深耕下去,它就不只是一个交差的毕设,而是你简历上拿得出手的真实项目。
