Django商城项目实战:源码拆解与部署性能优化
2026/9/11 19:13:32 网站建设 项目流程

简介:这是一份基于Django与Python开发的美多商城项目源码,定位为课程设计、毕业设计或编程入门练手资源,适合计算机、人工智能、通信工程等专业学生下载参考。项目采用Django框架组织代码,包含项目配置文件、路由映射、WSGI服务入口、应用初始化逻辑以及SQLite数据库文件,结构简洁,便于快速理解Django项目的启动流程与基本目录规范。压缩包共6个文件,以5个.py源码文件为主,另含1个sqlite3数据库文件,整体大小仅3KB,属于轻量级商城示例工程。当前已有1034人学习浏览,代码经过测试可运行,下载后可在本地环境直接验证,也可在此基础上扩展商品展示、用户登录、购物车、订单管理等模块,适合二次开发与功能完善。

1. 从压缩包到可运行项目:Django商城源码的正确打开方式

拿到一个命名为“基于Django+python开发的美多商城项目源码.zip”的压缩包,第一反应通常是解压、配环境、跑起来。但做过几个Django项目的人都会有同感:真正耗时间的不是Django本身,而是项目里那些约定俗成的目录规范、配置拆分方式和业务模块之间的耦合关系。美多商城作为一套典型的B2C电商系统,覆盖了用户、商品、购物车、订单、支付、后台管理等电商全链路模块,几乎把Django开发中会遇到的常见问题都演示了一遍。

这篇文章不打算逐文件解读某个特定版本的源码,而是按一线工程师拿到这类项目后的处理路径来讲:先看懂项目结构和配置,再把核心业务模块拆开看数据模型与查询逻辑,接着处理认证与权限,最后落到部署上线和性能排查。读完之后,你不仅能把这套源码跑起来,还能在自己动手写商城类项目时直接复用里面的设计思路。适合已经会用Django做简单CRUD、想往完整项目方向进阶的开发者,也适合准备基于Django做毕业设计或企业内部商城系统的团队参考。

2. Django项目结构与配置:拿到源码后先改这几处

2.1 settings.py里的隐藏依赖:从SECRET_KEY到数据库配置

解压源码后第一件事不是急着python manage.py runserver,而是检查配置文件。大多数Django项目源码的settings.py里,SECRET_KEY是直接写死的,数据库连接的账号密码也可能是开发环境的默认值。常见做法是先创建虚拟环境,再安装依赖,最后逐项核对配置项。

# 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate # 安装项目依赖 pip install -r requirements.txt # 生成新的SECRET_KEY(不要用源码里自带的) python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

这段命令行做了三件事:建立隔离的Python运行环境,避免和系统全局包冲突;通过requirements.txt安装Django、mysqlclient、redis等依赖包;重新生成SECRET_KEY,防止因源码泄露导致的会话伪造风险。拿到新密钥后,把它写进settings.py或环境变量文件里。

数据库配置是第二个必改项。美多商城这类项目通常用MySQL存储业务数据、Redis做缓存和Session存储。settings.py里对应的配置大致是这样的:

# settings.py 数据库与缓存配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'meiduo_mall', 'USER': 'root', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': 3306, } } CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } }

配置里值得注意的有两个点。第一,ENGINE用的是django.db.backends.mysql,这意味着本地环境必须装好mysqlclient库,而mysqlclient在Linux和Windows上的安装方式不同——Windows通常需要预编译的whl文件,Linux则依赖libmysqlclient-dev系统包。第二,CACHES的BACKEND指向django_redis,这个配置同时承担了Session存储和页面缓存的功能,如果你发现登录后Session不生效,多半是Redis服务没启动。

2.2 目录结构与App拆分逻辑

美多商城源码的目录组织方式代表了一类Django项目的典型布局:项目根目录下有manage.py和一个与项目同名的包,里面是settings、urls、wsgi等核心配置;业务功能按App拆分,每个App独立维护自己的models、views、urls和admin。商城类项目通常会把用户、商品、购物车、订单、支付分别拆成独立App,这样做的直接好处是团队协作时互不干扰,坏处是App之间的关联查询变多,需要靠外键或信号机制来同步数据。

meiduo_mall/ ├── manage.py ├── meiduo_mall/ # 项目配置包 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ ├── goods/ # 商品模块 │ ├── carts/ # 购物车 │ ├── orders/ # 订单 │ ├── payment/ # 支付 │ └── admin/ # 管理后台 └── utils/ # 通用工具函数

把业务App统一放在apps目录下,是Django项目从简单走向复杂时最常见的结构调整方式。需要在settings.py中通过sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))把apps目录加入模块搜索路径,否则运行时import会报ModuleNotFoundError。如果你在源码里看到的不是这种布局,而是所有App与manage.py平级,那也是合理的组织方式,关键要理解每种布局对应的导入路径和部署配置差异。

3. 商品、购物车与订单:电商核心业务的数据模型与查询优化

3.1 商品模型的SPU与SKU设计

商城类项目的核心在于商品模型的设计。美多商城这类项目通常采用SPU(Standard Product Unit,标准化产品单元)与SKU(Stock Keeping Unit,库存量单位)两级结构:SPU代表商品本身,比如“华为Mate 60”;SKU代表具体规格组合,比如“华为Mate 60 黑色 256GB”。这种设计的价值在于,商品详情页展示的信息(参数、图片、描述)挂在SPU上,而价格、库存、销量挂在SKU上,二者通过外键关联。

# apps/goods/models.py 商品模型核心代码 from django.db import models class SPU(models.Model): """标准化产品单元""" name = models.CharField(max_length=128, verbose_name='商品名称') desc = models.TextField(verbose_name='商品描述') category = models.ForeignKey('Category', on_delete=models.PROTECT) class SKU(models.Model): """库存量单位""" spu = models.ForeignKey(SPU, on_delete=models.CASCADE, related_name='skus') spec_values = models.JSONField(verbose_name='规格值', default=list) price = models.DecimalField(max_digits=10, decimal_places=2) stock = models.IntegerField(default=0) sales = models.IntegerField(default=0, db_index=True)

设计上要特别注意两点。第一,SPU被SKU引用时用了on_delete=models.CASCADE,意味着删除SPU会连带删除所有SKU,这在管理后台中如果误操作会造成商品数据批量丢失。如果要在生产环境限制这种行为,应该改成models.PROTECT,让系统阻止删除仍被引用的SPU。第二,spec_values使用JSONField存储规格值,这是Django 3.1以后才稳定的字段类型,如果源码版本较老,可能会用逗号拼接的CharField代替,查询规格组合时需要先拆分再匹配。

商品列表页的查询优化是另一个常见考点。SKU表本身数据量不大,但商品列表往往要展示价格、销量、图片、分类名称等关联信息,如果直接用ORM遍历并逐条访问关联对象,会产生N+1次查询。常见做法是用select_relatedprefetch_related提前把关联数据加载到内存:

# 商品列表视图中的查询优化 sku_list = SKU.objects.select_related('spu')\ .prefetch_related('sku_images')\ .filter(is_launched=True)\ .order_by('-sales')[:20]

这段代码里,select_related('spu')会生成一条SQL JOIN语句一次性把SPU表数据取回来,适用于一对一或多对一关系;prefetch_related('sku_images')则额外发一条查询把关联图片全部取出,然后按外键分组,适用于多对多或反向多对一关系。如果不做这两个优化,20个SKU商品可能要额外执行40到60条SQL。Django的ConnectionQueries日志是验证优化效果的直尺,在settings.py里配置LOGGING或在视图中临时输出len(connection.queries)就能看到查询次数差异。

3.2 购物车的Redis存储结构与合并逻辑

购物车是商城项目里对存储结构设计考验最大的模块。如果直接建一张购物车表存到MySQL,每次加购、改数量、勾选都要读写数据库,频繁的读写会拖慢响应速度。美多商城这类项目通常把购物车数据放Redis,用Hash结构存储,key为用户ID,field为SKU ID,value为商品数量。

# apps/carts/views.py 购物车核心操作 import json from django_redis import get_redis_connection def add_cart(request, sku_id, count): user = request.user redis_conn = get_redis_connection("default") cart_key = f"cart_{user.id}" # 检查商品库存 sku = SKU.objects.get(id=sku_id) if sku.stock < count: return JsonResponse({'code': 400, 'errmsg': '库存不足'}) # Hash操作:hincrby实现数量累加 redis_conn.hincrby(cart_key, sku_id, count) return JsonResponse({'code': 200, 'cart_count': redis_conn.hlen(cart_key)})

这里用hincrby而不是先hgethset,是因为Redis的INCR类命令是原子操作,在高并发场景下不会出现数量加减相互覆盖的问题。hlen用来获取购物车中SKU的种类数,而不是总件数,前端展示“购物车(5)”通常指的是5种商品、共若干件,这个语义差异在联调时容易搞混。

登录状态下的购物车还有一个常见需求:用户未登录时把商品加进购物车,登录后需要合并本地购物车和Redis购物车。处理逻辑是先取本地Cookie中的购物车数据,遍历后对Redis执行hincrby,合并完成后删除Cookie。删除对象时Django提供了delete()方法,但要注意它不会触发save()信号,如果有级联清理需求需要手动处理。

3.3 订单模块的事务处理与状态流转

订单模块是电商系统里对数据一致性要求最高的部分。用户下单时既要扣减库存,又要生成订单记录,还要清空购物车中对应的商品,这三步操作如果中间某一步失败,会造成库存扣了但订单没生成、或者订单生成了但购物车没清掉的脏数据。Django解决这个问题的标准做法是用transaction.atomic()包裹整个业务流程。

# apps/orders/views.py 下单事务处理 from django.db import transaction from django.db.models import F def create_order(request): """创建订单,涉及库存扣减与购物车清理""" user = request.user cart_items = get_cart_items(user) # 获取购物车所选商品 with transaction.atomic(): order = Order.objects.create( user=user, total_amount=0, pay_method=request.POST.get('pay_method') ) order_items = [] for sku_id, count in cart_items.items(): sku = SKU.objects.select_for_update().get(id=sku_id) if sku.stock < count: transaction.set_rollback(True) return JsonResponse({'code': 400, 'errmsg': f'{sku.name} 库存不足'}) # F()表达式避免并发扣减超卖 sku.stock = F('stock') - count sku.sales = F('sales') + count sku.save() order_items.append(OrderItem( order=order, sku=sku, count=count, price=sku.price )) OrderItem.objects.bulk_create(order_items) clear_selected_cart(user) # 清理已下单的购物车 return JsonResponse({'code': 200, 'order_id': order.id})

事务代码里有三个细节值得说明。第一,select_for_update()会对SKU记录加行级锁,锁在事务提交或回滚时释放,这样两个并发请求同时下单时不会同时读到同一个库存值。第二,F('stock') - count把库存扣减操作下推到数据库层面执行,避免了“读出来、减一、写回去”这种三步操作在并发下的竞态条件。第三,transaction.set_rollback(True)是手动标记事务回滚的常用方式,它比抛异常更温和,因为异常会被框架捕获并记录日志,而set_rollback直接走回滚分支。

4. 用户认证与JWT登录:从Session到Token的演进路径

4.1 Django默认认证体系的边界

Django自带的认证系统基于Session,用户登录后服务端把Session ID写入Cookie,浏览器后续请求自动携带这个ID。在小规模项目和后台管理系统中,这套方案已经够用,但放到商城项目里会有两个明显痛点:一是Session数据默认存在数据库表中,用户量上来后这张表会成为读写瓶颈;二是如果项目需要做前后端分离,原生App或小程序端无法直接操作Cookie,Session机制对接起来十分别扭。

美多商城这类项目普遍的做法是把Session存储迁移到Redis,同时为用户登录接口引入JWT(JSON Web Token)认证。JWT的核心逻辑是服务端不保存用户状态,而是把用户ID、过期时间等信息加密后生成一个字符串下发到客户端,客户端后续请求在Authorization头中携带这个字符串,服务端验签后即可识别用户身份。

4.2 在Django中集成JWT认证的落地步骤

要在Django中实现JWT认证,最常用的是djangorestframework-simplejwt这个扩展包,配合Django REST Framework使用。安装后需要按以下步骤调整配置:

# settings.py JWT与DRF配置 INSTALLED_APPS = [ # ... 'rest_framework', 'rest_framework_simplejwt', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], } from datetime import timedelta SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'AUTH_HEADER_TYPES': ('Bearer',), }

ACCESS_TOKEN_LIFETIMEREFRESH_TOKEN_LIFETIME这对参数需要重点理解。访问令牌的存活时间短(30分钟),用于每次请求的身份验证;刷新令牌的存活时间长(7天),用于访问令牌过期后无声续期。AUTH_HEADER_TYPES声明了前端请求头中Token的前缀格式,常见样式是Authorization: Bearer eyJhbGciOi...,如果你发现前端传了Token但接口仍返回401,先去检查前缀是否匹配。

集成完成后,登录视图可以直接使用simplejwt提供的TokenObtainPairView,也可以自定义一个登录接口,在验证用户名密码后额外把用户信息合并进Token的payload中:

# apps/users/views.py 自定义JWT登录 from rest_framework_simplejwt.tokens import RefreshToken def login(request): username = request.data.get('username') password = request.data.get('password') user = authenticate(username=username, password=password) if user is None: return Response({'code': 400, 'errmsg': '用户名或密码错误'}) refresh = RefreshToken.for_user(user) refresh['username'] = user.username refresh['is_admin'] = user.is_staff return Response({ 'code': 200, 'access': str(refresh.access_token), 'refresh': str(refresh), 'username': user.username })

RefreshToken.for_user(user)底层调用了Django的user model生成包含user_id的Token,这是simplejwt识别用户身份的基石。把extra信息塞进Token后,在视图函数里通过request.user拿到的仍然是User对象,而不是Token里的字典,所以如果想读取Token中额外的username字段,需要用request.auth.payload.get('username')这样的方式获取。

4.3 登录接口的安全防护细节

登录接口是商城项目被攻击的重灾区。密码错误尝试、撞库、批量注册都是常见风险,源码里的登录视图一般只做了最基础的验证,生产环境需要额外补上几个防护层。限流可以用Django REST Framework自带的AnonRateThrottle,对未认证用户按IP限制每分钟请求次数;密码存储务必确认Django版本高于2.1,因为旧版本的PBKDF2迭代次数过低,暴力破解成本会显著下降。

用户模块还有一个容易被忽略的配置项:AUTH_USER_MODEL。如果你在源码中发现项目自定义了User模型(比如加上了手机号、头像字段),必须在第一次迁移之前就配置好:

# settings.py 自定义用户模型 AUTH_USER_MODEL = 'users.User'

这个配置项必须在首次执行makemigrations之前设置,一旦Django默认的auth_user表已经建好,再切换自定义用户模型会报出一连串外键约束错误。新项目拿到手,先检查settings里有没有这一行,没有的话趁数据库还没初始化赶紧补上。

5. Django Admin后台与商品管理:让运营人员参与内容维护

5.1 注册模型与自定义展示字段

Django自带的Admin后台是商城项目运营中不可跳过的一环。美多商城的运营需要维护商品分类、SPU/SKU信息、轮播图、订单状态等数据,直接操作数据库太危险,写一套定制管理页面又费时费力。利用Admin的ModelAdmin机制,可以在几十分钟内把核心模型的增删改查页面配置到可交付状态。

# apps/goods/admin.py 商品模型的管理后台配置 from django.contrib import admin from .models import SPU, SKU, Category @admin.register(SPU) class SPUAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'category', 'created_time') list_filter = ('category',) search_fields = ('name',) list_per_page = 20 @admin.register(SKU) class SKUAdmin(admin.ModelAdmin): list_display = ('id', 'spu', 'price', 'stock', 'sales', 'is_launched') list_editable = ('price', 'stock', 'is_launched') list_filter = ('is_launched',) search_fields = ('spu__name',)

list_display决定列表页展示哪些列,list_editable允许运营人员直接在列表页修改价格和库存,这对频繁调价的电商场景来说非常实用,省去了一行行点击进入编辑页面的操作。search_fields里写了spu__name这种跨表查询的写法,这个双下划线语法在Django的ORM中随处可见,Admin的搜索框会自动把它转换成关联表的LIKE查询。

5.2 使用admin.site.header定制品牌与后台样式

默认的Django Admin界面样式比较朴素,上线给运营用之前,通常需要做一点品牌化调整。不需要写前端代码,只要在admin.py里覆盖几个属性即可:

# 在任意App的admin.py中设置 admin.site.site_header = "美多商城管理后台" admin.site.site_title = "美多商城" admin.site.index_title = "商品与订单管理"

这三个属性分别控制后台登录页顶部的品牌文字、浏览器标签页标题和首页的欢迎语。如果还不够,Django Admin支持通过重写base_site.html模板来插入自定义CSS或Logo图片,把模板文件放在templates/admin/base_site.html路径下就能覆盖默认模板,这是后台美化最直接的方式。

5.3 Admin后台的商品批量上下架操作

商城运营经常会遇到“换季商品统一下架”或“活动商品批量上架”的需求。Admin的actions机制让自定义批量操作变得十分轻量:

# apps/goods/admin.py 批量上下架操作 from django.contrib import admin def make_launched(modeladmin, request, queryset): queryset.update(is_launched=True) make_launched.short_description = "批量上架" def make_unlaunched(modeladmin, request, queryset): queryset.update(is_launched=False) make_unlaunched.short_description = "批量下架" class SKUAdmin(admin.ModelAdmin): # 已有的配置... actions = [make_launched, make_unlaunched]

queryset.update()queryset.filter()一样,都是直接在生产数据库层面执行的批量操作,不会加载单个对象到内存后再逐条save,因此在几千条SKU上执行也很快。需要注意的是,update()不会触发模型的save()方法,如果SKU模型重写了save来做缓存清理之类的额外动作,批量操作会跳过这些逻辑,需要在action函数里手动补充。

6. 从开发到上线:Django商城部署与性能调优要点

6.1 使用宝塔面板部署Django项目的可操作步骤

本地开发跑通之后,部署到服务器是Django项目生命周期中绕不开的一环。用宝塔面板部署Django是当前中小型项目最常见的选择,它把Python环境管理、Nginx反向代理、MySQL/Redis安装这些过程都图形化了。按以下步骤走一遍,基本不会出现环境层面的卡壳。

# 第一步:在宝塔面板中安装Python项目管理器 # 添加Python版本,建议选择3.8或3.10(取决于项目依赖) # 第二步:创建项目时选择对应的Python版本 # 项目路径填 /www/wwwroot/meiduo_mall # 启动方式选 gunicorn # 启动文件填 meiduo_mall.wsgi:application

这里的关键选择是Web服务器架构:Nginx负责处理静态文件和转发请求,Gunicorn作为Python WSGI服务器运行Django应用。设置好Nginx反向代理后,需要在/www/server/panel/vhost/nginx下的站点配置里加一段静态文件处理规则:

location /static/ { alias /www/wwwroot/meiduo_mall/static/; expires 7d; } location /media/ { alias /www/wwwroot/meiduo_mall/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

配置里expires 7d让浏览器对静态资源缓存7天,能显著压缩重复请求的流量。proxy_set_header三行虽然看起来机械,但缺了它们会导致Django拿不到客户端的真实IP,request.META['REMOTE_ADDR']会恒等于127.0.0.1,这会影响限流和日志分析的正确性。

如果项目用到了Celery处理异步任务(比如发送短信验证码、订单超时关闭),还需要在宝塔的进程守护管理器里添加Celery Worker和Beat的常驻运行配置,这两者的区别是Worker执行任务,Beat定时向队列投递任务。

6.2 settings.py为上线必改的安全与性能配置

把DEBUG从True改成False只是上线的第一步,还有几项配置直接影响线上环境的安全与运行效率:

# 生产环境settings.py关键配置 DEBUG = False ALLOWED_HOSTS = ['www.yourdomain.com', 'yourdomain.com'] # 静态文件收集目录 STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 安全相关 SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_BROWSER_XSS_FILTER = True SESSION_COOKIE_SECURE = True # 全站HTTPS时开启 CSRF_COOKIE_SECURE = True # 日志记录 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'ERROR', 'class': 'logging.FileHandler', 'filename': '/www/wwwroot/meiduo_mall/logs/error.log', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'ERROR', 'propagate': True, }, }, }

ALLOWED_HOSTS配置是Django的安全边界,不在列表中的Host访问会直接返回400。如果不配置或留空列表,DEBUG=False时所有请求都会被拒绝。日志配置确保线上环境发生500错误时有迹可循,否则排查问题时只能靠Gunicorn的控制台输出,一旦进程重启,错误信息就丢了。

配置改完后,需要执行python manage.py collectstatic把静态文件集中到STATIC_ROOT目录,否则Nginx的/static/规则会找不到文件,页面样式全部丢失。这一步在Django部署流程中是最容易被新手遗漏的。

6.3 接口响应变慢时的排查路径

线上接口变慢是Django项目最常见的故障,排查路径按性价比排列依次是:数据库慢查询、缓存命中率、ORM查询次数。

数据库慢查询优先通过MySQL的slow query log查看,生产环境建议在MySQL配置中加一行slow_query_log = 1long_query_time = 2,超过2秒的SQL会被记录。拿到慢SQL后回过来检查对应的ORM语句,explain一下看是否走了索引,SKU表的外键和销量字段都要建索引。

缓存命中率可以用Redis的INFO命令查看keyspace_hitskeyspace_misses的比值。商品详情页的缓存策略一般是“SKU维度 + 短过期时间”,比如缓存SKU信息5分钟,运营改了价格后最多5分钟生效,这比完全不缓存的效果好很多,实现方式可以借助Django内置的cache_page装饰器:

# 商品详情页添加5分钟缓存 from django.views.decorators.cache import cache_page @cache_page(60 * 5, key_prefix='sku_detail') def sku_detail(request, sku_id): # 原有查询逻辑

如果你发现接口慢以外还伴随着CPU飙高,常见原因是Gunicorn的Worker数量配置不合理。gunicorn meiduo_mall.wsgi:application -w 4 -b 127.0.0.1:8000这里的-w参数,经验值是CPU核数的2倍加1,过少的Worker会让请求排队,过多的Worker会因上下文切换反而拖慢性能。调整这个参数后重新加载Gunicorn,在线上环境能立竿见影地改善响应时间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询