
简介Python基于Django框架的商城管理系统是一款面向高校Python课程设计与期末大作业的完整项目包覆盖商城后端逻辑与前端页面的基础实现。系统已获导师指导并取得97分高分下载后无需修改即可运行非常适合希望快速完成作业或通过完整案例学习Django实战流程的读者。压缩包共980个文件约52.67MB其中png/jpg等图片资源用于商品与界面展示html/css/js构成前端页面py/pyc为Django业务逻辑与视图代码sql与sqlite3则是数据库结构及初始数据无需额外配置即可连接前端资源类型齐全整体目录结构清晰便于按模块查看和二次扩展。已有272人学习下载项目包含商城管理常见功能模块既能满足课程验收要求也可作为毕业设计的参考范本尤其适合需要快速搭建可演示系统的初学者。1. 期末大作业里的 Django 商城系统先看它值不值得你改打开那个Python基于Django框架的商城管理系统源码数据库期末大作业.zip第一眼感觉大概是“东西不少但不知道从哪下手”。这类项目压缩包在大学期末里几乎是标配一个 Django 写的 B2C 商城带订单、购物车、后台管理再配一份 SQL 或 SQLite 数据库文件。它能解决的核心问题是“如何在一个限定期限内交付一个演示效果完整、能跑通前后端闭环、还能在答辩时讲清楚设计原理的 Web 应用”。如果你正准备交期末作业或者是刚学会 Python 基础想看看真实项目长什么样这套东西的阅读价值比你自己从零写要高很多。但它的坑也明显很多大作业项目用的是老版本 Django、依赖锁得死、数据库路径写死、代码注释少得可怜。这篇文章按照一线开发者的视角把这个压缩包拆成“模型层、视图层、模板层、部署链路”告诉你拿到手后第一件事做什么、哪些代码是核心不能删、哪些字段是凑数的可以大胆改。内容按“理论先立住、再动手能复现”的顺序走中间会直接把可落地执行的命令、配置代码和你答辩时一定会被问的参数写出来。2. 解压之后先懂目录结构与数据库设计再谈二次开发2.1 Django 商城系统的目录套路apps 按业务拆配置集中在 settings绝大多数期末 Django 商城项目会采用多 app 结构而不是把所有逻辑塞进一个views.py。这是你首先要看懂的地方。常见结构长这样shopping_mall/ ├── manage.py ├── shopping_mall/ # 项目配置目录 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块注册/登录 │ ├── goods/ # 商品模块商品列表/详情 │ ├── cart/ # 购物车模块 │ ├── order/ # 订单模块下单/支付回调 │ └── adminx/ # 自定义后台管理 ├── static/ # 静态文件 ├── templates/ # 存放所有 HTML 模板 ├── db.sqlite3 └── requirements.txt如果你拿到的压缩包是这种“项目配置 业务 app”的布局说明写这个项目的人有一定工程意识改起来会舒服。另一种常见布局是只有一个 app 叫mall或shopviews 文件里堆了几百行代码这种结构也不是不能跑但你要做的第一件事就是把路由和视图的对应关系画出来否则后面加功能容易迷路。在动手之前建议先用一条命令验证整个项目能不能在当前环境里跑起来。先创建虚拟环境然后安装依赖最后跑开发服务器python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt python manage.py migrate python manage.py runserver 0.0.0.0:8000注意看requirements.txt里 Django 的版本。图书馆里常见的期末项目有 Django 2.2、3.2 甚至 4.x。如果你本机是python3.10Django 2.2 可能存在兼容性问题跑migrate时报错的话建议直接把 Django 升级到当前稳定版的 4.2 LTS 或 5.0然后再通过python manage.py check逐层排除错误。升级之后大概率遇到的报错是django.core.exceptions.ImproperlyConfigured或模板标签语法变化大部分都好修。2.2 从数据库文件反推核心表读懂“商品表、订单表、用户表”的字段你就读懂了业务解压后你会得到一个数据库文件。如果是db.sqlite3直接用 Python 自带模块就能看表结构不需要额外安装数据库工具。如果你手里的是.sql文件那对应的是 MySQL 导出导入的时候要把字符集选成utf8mb4否则中文商品名会乱码。用 Django 的 inspectdb 是最高效的逆向方式。先把数据库连接配好或者直接读取 sqlite 文件python manage.py inspectdb --database default models_backup.py这个命令会从现有数据库逆向生成 ORM 模型文件但你一般不需要真的去替换项目里的 models.py用它对照着看字段即可。核心要看的表就三张用户表UserProfile 或 Account除了继承自AbstractUser的字段外商城系统一般会扩展手机号、收货地址、积分这几个字段。很多大作业在注册逻辑里用的是form.save()并没有处理扩展字段导致数据表里mobile或address始终为空答辩时被问“为什么注册后手机号没存进去”就会卡住。商品表Goods 或 Product核心字段包括name、price、stock、sales、image、category。要注意这里是否有is_on_sale或status字段如果没有说明项目不支持商品上下架后续你做“库存扣减”时可能要自己补。订单表Order 和 OrderGoods主订单表管理总金额、状态、用户、下单时间订单商品明细表记录每一件商品的下单价和数量。两者通过order_id外键关联。这是商城系统里最重要的一对多关系答辩时你把这个讲清楚基本就稳了。表格形式把三张核心表要重点看的字段拉出来方便你对着数据库里找数据表必看字段业务含义不看会怎样用户扩展表mobile/address用户联系与收货信息注册页面收集了但入库为空商品表price/stock/is_sale价格、库存、上下架无法控制可售商品范围订单主表order_sn/status/total_amount唯一编号、状态机、金额支付回调时无法对账订单明细表goods_id/count/price商品快照商品改价后订单总金额对不上2.3 数据库初始化三件套migrate、createsuperuser、loaddata拿到源码后最稳的起步方式不是直接跑runserver而是先重置数据库到初始状态。因为源码包里自带的db.sqlite3可能带着原作者的一堆测试数据图片路径都是绝对的轮播图的 URL 也许都失效了。建议这样操作python manage.py migrate --run-syncdb python manage.py createsuperuser python manage.py runserver如果你不想要原来数据库里的脏数据直接把db.sqlite3删除然后重新跑迁移。rm -f db.sqlite3 python manage.py migrate python manage.py createsuperuser进到 Django admin 后台之后把原来的测试商品清掉重新录入几条数据作为演示商品。这里要特别提醒很多期末项目的商品图片字段是 URLField 而不是 ImageField你上传图片到本地服务器的 media 目录后页面还是显示不了因为 URLField 期望的是一个完整的 http 地址。如果是这种情况一种快速改法是直接存相对路径/media/goods/xxx.jpg然后在模板里加{% load static %}或使用MEDIA_URL拼接。另一种就是改模型字段类型为 ImageField然后重新迁移这是最省事、答辩时也最容易被理解的方案。3. 商品列表与购物车模块是系统的命脉从 ORM 查询到 session 与数据库两级存储3.1 基于类的视图(ListView) 还是函数视图大作业一般更喜欢函数视图如果你打开goods/views.py大概率能看到两种风格老项目清一色def goods_list(request)风格函数里先filter后render新一点的项目则可能用class GoodsListView(ListView)模板名和上下文对象名都是约定俗成的写法。函数视图的问题在于代码重复度高但它的优点是调试直观断点打进去就能看到完整流程。而类视图配合 Django 内置的混入类可以实现搜索、分页、排序的代码复用。这里用一个最典型的商品搜索分页组合作为例子# goods/views.py from django.shortcuts import render from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from .models import Goods def goods_list(request): # 获取商品列表按上架时间倒序 goods Goods.objects.filter(is_saleTrue).order_by(-created_at) # 关键词搜索q 参数来自前端 keyword request.GET.get(q, ) if keyword: goods goods.filter(name__icontainskeyword) # 分类过滤 category_id request.GET.get(category_id, ) if category_id: goods goods.filter(category_idcategory_id) # 分页 paginator Paginator(goods, 12) page_number request.GET.get(page, 1) try: page_obj paginator.page(page_number) except PageNotAnInteger: page_obj paginator.page(1) except EmptyPage: page_obj paginator.page(paginator.num_pages) return render(request, goods/goods_list.html, {page_obj: page_obj})这段代码里有几个关键点答辩常考。goods.filter(name__icontainskeyword)对应 SQL 里的WHERE name LIKE %keyword%icontains表示不区分大小写order_by(-created_at)是ORDER BY created_at DESC的 ORM 写法分页的核心逻辑是Paginator(goods, 12)每次查询只取 12 条数据通过切片实现 LIMIT/OFFSET。如果你在页面上看到“上一页/下一页”的按钮失效多半是模板里没有把request.GET的查询参数重新拼接到翻页链接上翻到第二页搜索关键词就丢了。3.2 购物车存储方案对比session 适合匿名用户数据库表适合登录用户商城系统的购物车模块有两种主流实现方式。第一种是完全基于 session 存储每次用户点“加入购物车”就把商品 ID 和数量存进request.session到结算页再从 session 读出商品信息。这种方案的好处是无需登录也可以加购实现简单坏处是 session 默认存在服务端用户量大时内存开销明显而且换个浏览器就找不到购物车记录。第二种是把购物车单独建一张表用户在未登录状态下也可以往表里写数据通过一个随机生成的device_id或guest_token标识身份登录后再把数据合并到用户 ID 下。第二种更接近真实商城答辩时讲出来也更亮眼。以下是一个典型的数据库购物车模型实现和加购逻辑# cart/models.py from django.db import models from django.conf import settings from goods.models import Goods class Cart(models.Model): user models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, nullTrue, blankTrue, verbose_name用户, ) goods models.ForeignKey(Goods, on_deletemodels.CASCADE, verbose_name商品) count models.PositiveIntegerField(default1, verbose_name数量) selected models.BooleanField(defaultTrue, verbose_name是否勾选) add_time models.DateTimeField(auto_now_addTrue, verbose_name添加时间) class Meta: unique_together (user, goods) verbose_name 购物车 verbose_name_plural verbose_name# cart/views.py from django.shortcuts import redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.http import JsonResponse from .models import Cart from goods.models import Goods login_required def add_cart(request): if request.method POST: goods_id request.POST.get(goods_id) count int(request.POST.get(count, 1)) goods get_object_or_404(Goods, idgoods_id) cart_item, created Cart.objects.get_or_create( userrequest.user, goodsgoods, defaults{count: count}, ) if not created: # 已存在则累加数量注意库存上限 cart_item.count count if cart_item.count goods.stock: cart_item.count goods.stock cart_item.save() return JsonResponse({code: 0, msg: 加入成功, count: cart_item.count}) return JsonResponse({code: 1, msg: 请求方式错误})这段实现里做了两个答辩高价值保护一是用get_or_create避免同一用户对同一商品重复插入记录二是累加数量时用goods.stock做了上限约束。这个上限约束其实对应了一个高频业务问题秒杀或者多人同时加购时如何避免超卖你可以在答辩时这样答库存的最终扣减要放在订单生成的事务里而不是加购时因为加购只是预占意向不可靠。这也自然引出了下一章的订单模块。3.3 模板层表格化呈现购物车用 Django template 语法渲染购物车页面要有“全选”“单选框”“数量加减”这些基础交互。这部分不需要 Vue 或 React原生 JavaScript 足够。模板中的核心渲染逻辑长这样!-- templates/cart/cart.html -- form idcart_form methodpost action{% url order:create_order %} {% csrf_token %} table classtable table-bordered thead tr thinput typecheckbox idcheck_all 全选/th th商品/th th单价/th th数量/th th小计/th th操作/th /tr /thead tbody {% for item in cart_items %} tr tdinput typecheckbox namegoods_ids value{{ item.goods.id }} checked/td td{{ item.goods.name }}/td td{{ item.goods.price }}/td td input typenumber namecount_{{ item.goods.id }} value{{ item.count }} min1 max{{ item.goods.stock }} /td td classsubtotal{{ item.goods.price|floatformat:2 }}/td tda href{% url cart:remove item.goods.id %}删除/a/td /tr {% empty %} trtd colspan6购物车还没有商品/td/tr {% endfor %} /tbody /table button typesubmit去结算/button /form注意{% url cart:remove item.goods.id %}这个写法它依赖 URLconf 里给路由起了 name# cart/urls.py from django.urls import path from . import views app_name cart urlpatterns [ path(add/, views.add_cart, nameadd), path(remove/int:goods_id/, views.remove_cart_item, nameremove), path(, views.cart_detail, namedetail), ]在这种写法下模板通过app_name:name方式动态解析 URL改了路由路径也不需要改模板这是 Django 里最基础但也最影响工程质量的细节。很多大作业项目虽然能跑用的却是a href/cart/remove/{{ item.goods.id }}/这种硬编码地址一旦 URL 结构调整页面就 404答辩演示时非常容易翻车。4. 订单与支付事务包裹住库存扣减支付回调要对得上订单号4.1 从购物车到订单多商品订单的设计与事务原子性订单模块是商城系统的重头戏也是答辩时老师最喜欢追问的部分。核心场景是用户从购物车勾选多个商品点击结算系统生成一张主订单和 N 条订单明细同时在事务里扣减商品库存。这里涉及一个非常关键的问题如果扣减库存成功但生成订单失败或者反过来系统就处于数据不一致状态。Django 的事务控制用transaction.atomic()实现可以把它理解成把多个数据库操作包成了一个不可分割的整体。下面是典型的创建订单视图核心代码# order/views.py from django.db import transaction from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from django.http import JsonResponse from .models import Order, OrderGoods from cart.models import Cart from goods.models import Goods import time import random login_required transaction.atomic def create_order(request): if request.method ! POST: return redirect(cart:detail) goods_ids request.POST.getlist(goods_ids) if not goods_ids: return JsonResponse({code: 1, msg: 未选中商品}) order_sn time.strftime(%Y%m%d%H%M%S) str(random.randint(1000, 9999)) order Order.objects.create( userrequest.user, order_snorder_sn, statuspending, total_amount0, ) total 0 goods_list Goods.objects.select_for_update().filter(id__ingoods_ids, is_saleTrue) for goods in goods_list: count_key fcount_{goods.id} count int(request.POST.get(count_key, 1)) if goods.stock count: # 库存不足直接抛异常触发事务回滚 transaction.set_rollback(True) return JsonResponse({code: 1, msg: f商品{goods.name}库存不足}) # 扣库存加销量 goods.stock - count goods.sales count goods.save() # 记录订单商品快照 OrderGoods.objects.create( orderorder, goodsgoods, goods_namegoods.name, goods_pricegoods.price, countcount, ) total goods.price * count order.total_amount total order.save() # 清空已购买的购物车记录 Cart.objects.filter(userrequest.user, goods_id__ingoods_ids).delete() return redirect(order:detail, order_idorder.id)这里有三点值得仔细说明。第一select_for_update()会生成SELECT ... FOR UPDATE语句对该商品行加锁直到事务结束。在并发场景下第二个请求要等第一个请求提交后才能读取同一行防止两个请求同时读到库存为 1 的冗余数据。但如果你的数据库是 SQLiteFOR UPDATE是空操作SQLite 通过数据库级写锁来保证串行化所以对期末项目来说演示效果不受影响。第二库存扣减放在创建订单明细之前是为了保证“只要有订单生成库存一定被正确扣减”。但真实电商里一般不是下单就扣库存而是支付成功才扣否则大量未支付订单会积压库存。第三订单明细为什么要存goods_name和goods_price快照字段因为商品表的价格可能调整管理员在后台改了商品原价历史订单里的金额也应该保持不变所以要把下单那一刻的名称和单价冗余到订单明细表里。这个设计在答辩里是亮点。4.2 支付模块怎么接支付宝沙箱环境和回调验签的落地路径期末项目里支付模块有两种层次一种是做个假支付按钮点击后直接把订单状态改成“已支付”简单但答辩容易被追问另一种是接入支付宝沙箱环境真实演示支付流程和异步回调这种完成度在期末答辩里属于加分项。支付宝沙箱接入的核心逻辑分三步构造支付请求参数、跳转支付宝页面、接收异步通知并验签。关键的views.py代码片段如下# order/views.py (支付宝沙箱简化示意) from alipay import AliPay, IS_ALIPAY_DEV def alipay_config(): # 读取应用私钥和支付宝公钥路径可自己定义 app_private_key_string open(keys/app_private_key.pem).read() alipay_public_key_string open(keys/alipay_public_key.pem).read() alipay AliPay( appid2021000123456789, app_notify_urlhttp://你的公网域名/order/alipay/notify/, app_private_key_stringapp_private_key_string, alipay_public_key_stringalipay_public_key_string, sign_typeRSA2, debugIS_ALIPAY_DEV, ) return alipay def alipay_pay(request, order_id): order Order.objects.get(idorder_id, userrequest.user) alipay alipay_config() order_string alipay.api_alipay_trade_page_pay( out_trade_noorder.order_sn, total_amountstr(order.total_amount), subjectf商城订单{order.order_sn}, return_urlhttp://你的公网域名/order/alipay/return/, notify_urlhttp://你的公网域名/order/alipay/notify/, ) return redirect(fhttps://openapi-sandbox.dl.alipaydev.com/gateway.do?{order_string})out_trade_no就是订单表里的唯一编号order_sn支付宝回调时会原样返回notify_url是异步通知地址支付宝服务器在用户支付成功后向这个地址发送 POST 请求。回调视图要有幂等性设计也就是支付宝的通知可能发送多次你的视图要能容忍重复通知不能重复给订单加状态或者重复改库存。简易写法如下def alipay_notify(request): alipay alipay_config() data request.POST.dict() signature data.pop(sign, ) success alipay.verify(data, signature) if success and data[trade_status] TRADE_SUCCESS: order_sn data[out_trade_no] order Order.objects.filter(order_snorder_sn).first() if order and order.status pending: order.status paid order.trade_no data[trade_no] order.paid_time timezone.now() order.save() return HttpResponse(success) return HttpResponse(failure)这段代码里有个隐藏点值得讲回调验签拿到 Order 后必须先判断当前状态是否为pending再执行更新。如果没有这个判断支付宝重复通知时你的逻辑可能会重复执行两次虽然save()相同的状态不会幂等损坏数据但如果你在其中加了积分、优惠券之类的副作用逻辑重复执行就会造成收益漏洞。这个代码里的“先查状态再更新”模式在真实支付系统里叫“状态机约束”答辩时可以展开讲。如果你的期末项目只要求本地演示不要求真实支付那直接在 create_order 成功后把状态置为paid即可但记得在答辩前把这一段替换代码注释掉不要让老师误以为你接入了真实支付。4.3 Django 执行查询与删除对象订单撤销的级联策略Django 在做删除操作时外键的on_delete参数决定了级联行为。典型场景是“用户取消订单”。常见选择有CASCADE级联删除明细和SET_NULL保留明细但不指向商品。订单明细保存的是历史快照如果商品被管理员删除订单明细应该保留所以OrderGoods.goods外键应该用SET_NULL而不是CASCADEclass OrderGoods(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_namegoods_list, verbose_name订单) goods models.ForeignKey(Goods, on_deletemodels.SET_NULL, nullTrue, blankTrue, verbose_name商品) goods_name models.CharField(max_length128, verbose_name商品名称快照) goods_price models.DecimalField(max_digits10, decimal_places2, verbose_name商品价格快照) count models.PositiveIntegerField(verbose_name数量)这里OrderGoods.order用CASCADE删除订单时明细一起删这是合理的goods用SET_NULL商品删除后订单明细仍然保留查历史记录时通过goods_name展示名称。related_namegoods_list则允许你在模板里用order.goods_list.all()反向查询明细比默认的ordergoods_set更可读。5. Django Admin 后台美化与自定义管理操作让大作业答辩多一个谈资5.1 注册模型与列表页展示字段比原生后台更符合商城管理习惯大多数期末项目的后台只是简单地把模型丢给 Django Admin没有做过任何定制。你只要做三件小事后台的演示效果会明显上一个档次。第一用list_display控制列表页显示哪些字段不要让商品列表只显示一个对象名称。第二用search_fields开启关键词搜索。第三用list_filter加筛选器。组合写法如下# goods/admin.py from django.contrib import admin from .models import Goods, Category admin.register(Goods) class GoodsAdmin(admin.ModelAdmin): # 列表页展示的字段 list_display (name, price, stock, sales, is_sale, created_at) # 右侧筛选栏 list_filter (is_sale, category) # 顶部搜索框 search_fields (name, category__name) # 每页条数 list_per_page 20 # 价格和库存可以直接在列表页编辑 list_editable (price, stock) # 默认排序按销量倒序 ordering (-sales,)list_editable代表列表页直接改字段这里的price和stock是运营最关心的两个字段列表页可编辑后演示效率高很多。search_fields里写category__name是利用跨表查询按分类名搜索这是 Django ORM 外键跨表的标准写法。如果你觉得默认的 Django Admin 样式太“原生”想快速美化可以使用django-simpleui这个第三方库安装后在INSTALLED_APPS里把simpleui放在django.contrib.admin之前即可不用改任何其他业务代码。pip install django-simpleui# settings.py INSTALLED_APPS [ simpleui, django.contrib.admin, # ... 其他 app ]simpleui最核心的收益只有一个不需要修改你的模型和视图代码它通过替换 admin 的模板来改变后台风格。启动后进入/admin你就能看到侧边栏菜单、卡片式图表这些对答辩展示来说足够体面。5.2 自定义 Admin Action 实现商品批量上下架期末商城的后台经常需要批量操作Django Admin 默认没有“批量上架/下架”的功能。自定义一个 Action 只需要写一个方法再注册到actions属性def make_sale(modeladmin, request, queryset): queryset.update(is_saleTrue) make_sale.short_description 标记为在售 def make_off_sale(modeladmin, request, queryset): queryset.update(is_saleFalse) make_off_sale.short_description 标记为下架 class GoodsAdmin(admin.ModelAdmin): actions [make_sale, make_off_sale]queryset.update()是一次 UPDATE 语句效率高而且不会触发模型的save()方法所以如果你的模型里重写了save()来维护某些冗余字段用 Action 批量修改时要留意这个区别。这个 Action 的代码量很少但能展示你对 Django Admin 扩展机制的理解答辩时可以主动讲一句“这是通过自定义 admin action 实现的”效果很好。6. 部署与交接宝塔面板部署 Django 项目的几个细节和避坑点6.1 本地到服务器gunicorn Nginx 代理静态文件期末项目交上去是源码包但如果能在自己的服务器上跑起来演示效果会完全不一样。常见做法是用宝塔面板部署 Django。核心路径是把 Django 应用用 gunicorn 跑起来让 Nginx 把 80 端口反向代理到 gunicorn 监听的本地端口。pip install gunicorn gunicorn shopping_mall.wsgi:application --bind 0.0.0.0:8000 --workers 2--workers 2表示启动 2 个 worker 进程一般按 CPU 核心数的 2 倍加 1 来设置。部署完成后Nginx 的站点配置里需要包含静态文件处理和反代规则server { listen 80; server_name your_domain.com; location /static/ { alias /www/wwwroot/your_project/static/; } location /media/ { alias /www/wwwroot/your_project/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; } }然后收集静态文件python manage.py collectstaticcollectstatic会把每个 app 的 static 目录和项目根目录的 static 文件统一收集到STATIC_ROOT指定的目录下Nginx 才能直接托管。6.2 敏感信息不入库把 SECRET_KEY 和数据库密码移到环境变量这是很多期末源码包被诟病的点settings.py里把SECRET_KEY、数据库密码全部写死。毕业工作后你会明白这个习惯是绝对的雷区。源码包如果传到公开仓库等于把密码直接暴露了。标准做法是使用环境变量import os SECRET_KEY os.environ.get(DJANGO_SECRET_KEY, local_dev_fallback_key) DEBUG os.environ.get(DJANGO_DEBUG, true).lower() true ALLOWED_HOSTS os.environ.get(DJANGO_ALLOWED_HOSTS, *).split(,)在服务器上用 systemd 或 supervisor 托管 gunicorn 时把环境变量写在启动配置里[Service] EnvironmentDJANGO_SECRET_KEYxxxx EnvironmentDJANGO_DEBUGfalse ExecStart/www/wwwroot/your_project/venv/bin/gunicorn shopping_mall.wsgi:application --bind 127.0.0.1:8000如果你用的是宝塔面板可以在“网站 → Python 项目 → 环境变量”里配置。6.3 最后一步验证部署结果并快速检查常见故障部署完成后不要急着关终端按下面顺序做一轮自测。先看进程是否存活ps -ef | grep gunicorn然后看 Nginx 是否正常反代curl -I http://127.0.0.1:8000如果返回502 Bad Gateway大概率是 gunicorn 没启动或者启动后崩了看日志journalctl -u your_django_service -n 50 --no-pager如果页面能打开但样式全丢先检查 Nginx 的location /static/配置和collectstatic是否执行成功。如果后台登录后跳转有问题大概率是ALLOWED_HOSTS没把你的域名加进去。这一轮跑完你这个基于 Django 的商城系统就算真正从“期末作业”升级成了“可演示、可部署、可交接”的完整项目。本文还有配套的精品资源点击获取