资讯动态

Django商城项目实战:源码拆解与部署性能优化

发布时间:2026/9/11 19:13:51 来源:尧图企业网站定制
简介这是一份基于Django与Python开发的美多商城项目源码定位为课程设计、毕业设计或编程入门练手资源适合计算机、人工智能、通信工程等专业学生下载参考。项目采用Django框架组织代码包含项目配置文件、路由映射、WSGI服务入口、应用初始化逻辑以及SQLite数据库文件结构简洁便于快速理解Django项目的启动流程与基本目录规范。压缩包共6个文件以5个.py源码文件为主另含1个sqlite3数据库文件整体大小仅3KB属于轻量级商城示例工程。当前已有1034人学习浏览代码经过测试可运行下载后可在本地环境直接验证也可在此基础上扩展商品展示、用户登录、购物车、订单管理等模块适合二次开发与功能完善。1. 从压缩包到可运行项目Django商城源码的正确打开方式拿到一个命名为“基于Djangopython开发的美多商城项目源码.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设计商城类项目的核心在于商品模型的设计。美多商城这类项目通常采用SPUStandard Product Unit标准化产品单元与SKUStock 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_length128, verbose_name商品名称) desc models.TextField(verbose_name商品描述) category models.ForeignKey(Category, on_deletemodels.PROTECT) class SKU(models.Model): 库存量单位 spu models.ForeignKey(SPU, on_deletemodels.CASCADE, related_nameskus) spec_values models.JSONField(verbose_name规格值, defaultlist) price models.DecimalField(max_digits10, decimal_places2) stock models.IntegerField(default0) sales models.IntegerField(default0, db_indexTrue)设计上要特别注意两点。第一SPU被SKU引用时用了on_deletemodels.CASCADE意味着删除SPU会连带删除所有SKU这在管理后台中如果误操作会造成商品数据批量丢失。如果要在生产环境限制这种行为应该改成models.PROTECT让系统阻止删除仍被引用的SPU。第二spec_values使用JSONField存储规格值这是Django 3.1以后才稳定的字段类型如果源码版本较老可能会用逗号拼接的CharField代替查询规格组合时需要先拆分再匹配。商品列表页的查询优化是另一个常见考点。SKU表本身数据量不大但商品列表往往要展示价格、销量、图片、分类名称等关联信息如果直接用ORM遍历并逐条访问关联对象会产生N1次查询。常见做法是用select_related和prefetch_related提前把关联数据加载到内存# 商品列表视图中的查询优化 sku_list SKU.objects.select_related(spu)\ .prefetch_related(sku_images)\ .filter(is_launchedTrue)\ .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为用户IDfield为SKU IDvalue为商品数量。# 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 fcart_{user.id} # 检查商品库存 sku SKU.objects.get(idsku_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而不是先hget再hset是因为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( useruser, total_amount0, pay_methodrequest.POST.get(pay_method) ) order_items [] for sku_id, count in cart_items.items(): sku SKU.objects.select_for_update().get(idsku_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( orderorder, skusku, countcount, pricesku.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或小程序端无法直接操作CookieSession机制对接起来十分别扭。美多商城这类项目普遍的做法是把Session存储迁移到Redis同时为用户登录接口引入JWTJSON 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(minutes30), REFRESH_TOKEN_LIFETIME: timedelta(days7), AUTH_HEADER_TYPES: (Bearer,), }ACCESS_TOKEN_LIFETIME和REFRESH_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(usernameusername, passwordpassword) 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_launchedTrue) make_launched.short_description 批量上架 def make_unlaunched(modeladmin, request, queryset): queryset.update(is_launchedFalse) 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拿不到客户端的真实IPrequest.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。如果不配置或留空列表DEBUGFalse时所有请求都会被拒绝。日志配置确保线上环境发生500错误时有迹可循否则排查问题时只能靠Gunicorn的控制台输出一旦进程重启错误信息就丢了。配置改完后需要执行python manage.py collectstatic把静态文件集中到STATIC_ROOT目录否则Nginx的/static/规则会找不到文件页面样式全部丢失。这一步在Django部署流程中是最容易被新手遗漏的。6.3 接口响应变慢时的排查路径线上接口变慢是Django项目最常见的故障排查路径按性价比排列依次是数据库慢查询、缓存命中率、ORM查询次数。数据库慢查询优先通过MySQL的slow query log查看生产环境建议在MySQL配置中加一行slow_query_log 1和long_query_time 2超过2秒的SQL会被记录。拿到慢SQL后回过来检查对应的ORM语句explain一下看是否走了索引SKU表的外键和销量字段都要建索引。缓存命中率可以用Redis的INFO命令查看keyspace_hits和keyspace_misses的比值。商品详情页的缓存策略一般是“SKU维度 短过期时间”比如缓存SKU信息5分钟运营改了价格后最多5分钟生效这比完全不缓存的效果好很多实现方式可以借助Django内置的cache_page装饰器# 商品详情页添加5分钟缓存 from django.views.decorators.cache import cache_page cache_page(60 * 5, key_prefixsku_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在线上环境能立竿见影地改善响应时间。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价