马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
从0到1拆解一个Django电商商城:源码结构、核心模块与踩坑记录
本文导读
最近帮朋友看了一个Django写的网上购物商城项目,前后端一个人搞定的那种,功能不算复杂但该有的都有——用户注册登录、商品分类搜索、购物车、订单、后台管理全齐了。这篇文章我把这个项目的源码拆开来讲,从目录结构到核心模块的代码,再到部署时踩的坑,尽量讲透。适合想用Django做Web项目、或者做毕设的同学参考。
一、项目概览:这个商城系统能干什么
先看功能模块,一个典型的B2C商城该有的功能基本都覆盖了:
图1 电商系统功能模块架构(前台、中台、后台三层结构) 模块 | 前台用户功能 | 后台管理功能 |
用户模块
|
注册、登录、个人信息修改、密码找回
|
用户列表、用户状态管理、权限分配
|
商品模块
|
分类浏览、关键词搜索、商品详情、规格选择
|
商品增删改查、分类管理、上下架
|
购物车
|
加入购物车、数量修改、删除、结算
|
—
|
订单模块
|
下单、订单列表、订单详情、取消订单
|
订单列表、发货、退款处理
|
内容模块
|
购物资讯浏览、通知公告查看
|
资讯发布、公告管理、轮播图配置
|
整体分前台和后台两部分,前台给普通用户用,后台给管理员用。前台用Django模板渲染,后台直接用Django Admin做了定制,省了很多事——这也是Django做项目快的原因之一,后台不用自己写。
二、技术栈选型:为什么用Django
做Web项目,Python生态里主要三个选择:Django、Flask、FastAPI。这个项目选了Django,我觉得是对的,原因如下:
框架 | 特点 | 适合场景 | 这个项目为什么不选 |
Django
|
全栈框架,自带ORM、Admin、Auth、表单
|
中大型项目、内容管理、电商
|
—(就是选的这个)
|
Flask
|
轻量微框架,自由度高,需要自己拼组件
|
小型API、原型、微服务
|
电商功能多,自己拼组件太费时间
|
FastAPI
|
异步高性能,自动生成API文档
|
高并发API、前后端分离
|
这个项目是模板渲染,不需要异步
|
Django的"全家桶"特性对这种功能完整的项目特别友好——用户认证用django.contrib.auth,后台用django.contrib.admin,数据库用ORM,表单用forms,连CSRF防护、分页、缓存都自带了。一个人开发的话,Django能让你把精力集中在业务逻辑上,而不是重复造轮子。
三、整体架构:MVT模式与项目结构
Django用的是MVT(Model-View-Template)模式,和传统的MVC有点像但叫法不同:Model对应数据层,View对应业务逻辑层,Template对应表现层。URL路由负责把请求分发到对应的View。
图2 D 项目的目录结构大概是这样:
shop/
├── manage.py # 项目管理脚本
├── shop/ # 项目配置目录
│ ├── settings.py # 全局配置(数据库、中间件、静态文件等)
│ ├── urls.py # 根路由
│ └── wsgi.py # WSGI入口
├── apps/
│ ├── user/ # 用户模块
│ │ ├── models.py # 用户模型(扩展Django User)
│ │ ├── views.py # 注册、登录、个人中心
│ │ ├── forms.py # 注册表单、登录表单
│ │ └── urls.py # 用户模块路由
│ ├── goods/ # 商品模块
│ │ ├── models.py # 分类、商品、规格
│ │ ├── views.py # 列表、详情、搜索
│ │ └── admin.py # 后台管理配置
│ ├── cart/ # 购物车模块(基于Session)
│ ├── order/ # 订单模块
│ └── content/ # 资讯、公告模块
├── templates/ # 模板目录
│ ├── base.html # 基础模板(导航、页脚)
│ ├── user/ # 用户相关页面
│ ├── goods/ # 商品相关页面
│ └── ...
└── static/ # 静态文件(CSS、JS、图片)
按功能模块拆app是Django的最佳实践,每个app只负责一块业务,代码组织清晰,后期维护也方便。我见过不少新手把所有代码都写在一个app里,到后期views.py几千行,改一个功能要翻半天——别这么干。
四、核心模块源码精读
4.1 用户认证模块
用户模块是整个系统的基础。Django自带了User模型,但字段不够用(没有手机号、昵称、头像),所以需要扩展。常用的扩展方式是用AbstractUser:
# apps/user/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class UserProfile(AbstractUser):
"""扩展用户模型"""
phone = models.CharField('手机号', max_length=11, blank=True)
nickname = models.CharField('昵称', max_length=50, blank=True)
avatar = models.ImageField('头像', upload_to='avatar/', default='avatar/default.png')
address = models.CharField('收货地址', max_length=200, blank=True)
class Meta:
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
然后在settings.py里指定AUTH_USER_MODEL = "user.UserProfile",这样Django就会用你自定义的用户表。注意这个配置一定要在第一次migrate之前设置好,后期改的话会很麻烦,数据库要重建。
注册视图用Django的Form组件做数据校验,比自己写if判断优雅得多:
# apps/user/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import UserProfile
class RegisterForm(UserCreationForm):
email = forms.EmailField(required=True, label='邮箱')
phone = forms.CharField(max_length=11, label='手机号')
class Meta:
model = UserProfile
fields = ('username', 'email', 'phone', 'password1', 'password2')
def clean_phone(self):
phone = self.cleaned_data['phone']
if not phone.startswith('1') or len(phone) != 11:
raise forms.ValidationError('请输入正确的手机号')
return phone
4.2 商品管理模块
商品模块是电商的核心。数据模型设计上,分类和商品是一对多,商品和规格是一对多:
# apps/goods/models.py
from django.db import models
class Category(models.Model):
name = models.CharField('分类名称', max_length=50)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, verbose_name='父分类')
sort = models.IntegerField('排序', default=0)
class Meta:
verbose_name = '商品分类'
ordering = ['sort']
class Goods(models.Model):
name = models.CharField('商品名称', max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类')
price = models.DecimalField('价格', max_digits=10, decimal_places=2)
stock = models.IntegerField('库存', default=0)
description = models.TextField('商品描述', blank=True)
image = models.ImageField('主图', upload_to='goods/')
is_on_sale = models.BooleanField('是否上架', default=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
verbose_name = '商品'
ordering = ['-created_at']
商品搜索用Django ORM的icontains做模糊查询,小项目够用了:
# apps/goods/views.py
from django.db.models import Q
from .models import Goods
def goods_search(request):
keyword = request.GET.get('keyword', '')
goods_list = Goods.objects.filter(
Q(name__icontains=keyword) | Q(description__icontains=keyword),
is_on_sale=True
)
return render(request, 'goods/list.html', {'goods_list': goods_list, 'keyword': keyword})
如果商品量大、搜索要求高,可以换Elasticsearch或者Whoosh,但毕设级别用icontains完全够。
4.3 购物车模块(基于Session)
购物车有两种实现方式:存数据库或者存Session。这个项目用的是Session,优点是用户不登录也能用,缺点是换设备就没了。对于小型商城,Session方案足够:
# apps/cart/views.py
from django.shortcuts import redirect
from goods.models import Goods
def add_to_cart(request, goods_id):
"""加入购物车"""
goods = Goods.objects.get(id=goods_id)
cart = request.session.get('cart', {})
if str(goods_id) in cart:
cart[str(goods_id)]['quantity'] += 1
else:
cart[str(goods_id)] = {
'name': goods.name,
'price': float(goods.price),
'quantity': 1,
'image': goods.image.url,
}
request.session['cart'] = cart
return redirect('cart:detail')
def cart_detail(request):
"""购物车详情"""
cart = request.session.get('cart', {})
total = sum(item['price'] * item['quantity'] for item in cart.values())
return render(request, 'cart/detail.html', {'cart': cart, 'total': total})
这里有个坑:Django默认Session存在数据库里,每次修改Session都会触发数据库写入。如果购物车操作频繁,可以把Session换成Redis缓存,性能会好很多。
4.4 订单模块与状态机
订单是电商最复杂的模块,核心是订单状态的流转。这个项目的订单状态比较简单:待付款→已付款→已发货→已完成,外加已取消。用一个状态字段控制:
# apps/order/models.py
from django.db import models
from user.models import UserProfile
class Order(models.Model):
STATUS_CHOICES = (
(0, '待付款'),
(1, '已付款'),
(2, '已发货'),
(3, '已完成'),
(4, '已取消'),
)
order_no = models.CharField('订单号', max_length=32, unique=True)
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name='用户')
total_amount = models.DecimalField('总金额', max_digits=10, decimal_places=2)
status = models.IntegerField('订单状态', choices=STATUS_CHOICES, default=0)
address = models.CharField('收货地址', max_length=200)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
goods_name = models.CharField('商品名称', max_length=100)
price = models.DecimalField('单价', max_digits=10, decimal_places=2)
quantity = models.IntegerField('数量')
订单号生成用时间戳+用户ID+随机数,保证唯一性:
import time
import random
def generate_order_no(user_id):
return f"{time.strftime('%Y%m%d%H%M%S')}{user_id}{random.randint(1000, 9999)}"
顺便说下前端怎么处理:订单列表页根据status显示不同的操作按钮,待付款显示"去付款",已发货显示"确认收货",已完成显示"再次购买"。这个用模板的{% if %}判断就行,不用写复杂的JS。
五、数据库设计:核心表结构
整个系统的核心实体关系如下:
图3 电商系统核心ER图(用户、订单、商品、分类) 主要表的设计要点:
表名 | 核心字段 | 设计说明 |
user_profile
|
username, phone, email, password
|
继承AbstractUser,扩展手机号和昵称
|
category
|
name, parent_id, sort
|
自关联实现多级分类
|
goods
|
name, category_id, price, stock, is_on_sale
|
价格用Decimal不用Float,避免精度问题
|
order
|
order_no, user_id, total_amount, status
|
订单号唯一索引,状态用choices约束
|
order_item
|
order_id, goods_name, price, quantity
|
冗余商品名称和价格,防止商品修改后订单数据变化
|
这里有个重要的设计原则:订单里的商品名称和价格要冗余存储,不能只存商品ID。因为商品的价格和名称可能会改,如果只存ID,以后查历史订单时显示的就是修改后的价格,就不对了。这个坑很多新手会踩。
六、部署与踩坑记录
项目开发完只是第一步,部署到服务器上能跑才是真的完。这个项目部署的时候踩了几个坑,给大家避避雷:
坑1:DEBUG=False后静态文件404
开发时DEBUG=True,Django会自动处理静态文件。上线时DEBUG=False,Django就不管静态文件了,需要用Nginx或者whitenoise来处理。解决方案是配置STATIC_ROOT,运行python manage.py collectstatic把静态文件收集到一个目录,然后让Nginx直接 serve 这个目录。
坑2:MySQL时区不对
用MySQL做数据库时,Django连接MySQL默认时区可能和服务器不一致,导致时间字段差8小时。在settings.py的DATABASES配置里加OPTIONS:{'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", 'charset': 'utf8mb4'},同时确保USE_TZ=True,TIME_ZONE='Asia/Shanghai'。
坑3:Session购物车在生产环境丢失
用uWSGI或Gunicorn多进程部署时,如果Session存在数据库里没问题,但如果存在缓存里,要确保所有进程连的是同一个缓存服务。另外Session的过期时间要设置合理,默认是两周,购物车数据存两周有点长,可以改成7天。
七、可扩展方向
这个项目是基础版,想做得更完善可以往这几个方向扩展:
1. 接入支付:支付宝沙箱或者微信支付,订单状态自动更新;
2. 商品规格:SKU模型,支持颜色、尺寸等多规格;
3. 优惠券/满减:营销活动模块,订单计算时叠加优惠;
4. 缓存优化:热门商品、分类列表用Redis缓存,减少数据库查询;
5. 异步任务:订单超时自动取消、邮件通知用Celery异步处理;
6. 前后端分离:后端写REST API(用Django REST Framework),前端用Vue或React重写。
八、常见问题FAQ
Q:Django做电商会不会性能不够?
A:中小规模完全够。Django的性能瓶颈通常在数据库查询,做好索引、加缓存、用select_related/prefetch_related优化查询,日PV几万到几十万没问题。真到了百万级,再考虑微服务拆分也不迟。
Q:购物车存在Session里,用户清了浏览器缓存就没了怎么办?
A:可以做双写——用户登录后把Session购物车同步到数据库,未登录时用Session。或者直接用数据库存购物车,要求用户必须登录才能加购,很多电商都是这么做的。
Q:Django Admin能直接当生产后台用吗?
A:内部管理用完全可以,Django Admin功能很强大,列表过滤、搜索、批量操作、表单校验都有。但如果是给商家用的多租户后台,就需要自己写了,Admin不适合多租户场景。
Q:这个项目做毕设够不够?
A:功能上完全够,但毕设要注意几点:一是要有需求分析和系统设计文档,不能只交代码;二是数据库设计要规范,画ER图;三是测试部分要写测试用例和测试结果;四是论文里要突出你的技术亮点,比如用了什么设计模式、做了什么优化。
九、总结与延伸学习路径
这个Django电商项目虽然不大,但覆盖了Web开发的核心知识点:MVT架构、ORM、表单验证、Session、用户认证、后台管理、数据库设计。把这个项目吃透,再做其他Web项目就触类旁通了。
如果想继续深入,可以按这个路径学习:
1. 先把Django官方文档过一遍,特别是ORM、Forms、Admin这几块,这是Django的精华;
2. 学Django REST Framework,把项目改造成前后端分离的API服务;
3. 学Redis和Celery,给项目加缓存和异步任务;
4. 学Docker和Nginx,掌握容器化部署和反向代理配置;
5. 最后可以研究一下微服务架构,把单体项目拆成多个服务。
做全栈开发,最重要的是动手。看十篇教程不如自己写一个项目,遇到问题查文档、搜Stack Overflow,踩过的坑都是你的经验。一个人搞定一个完整项目,那种成就感是看教程体会不到的。
—— 全栈开发的乐趣就在于,从数据库到前端页面,从代码到部署,每一层都能自己掌控。
有问题欢迎评论区交流,需要完整项目结构和配置文件模板可以关注后续更新~
|