☰
基于Python和Django的网上产品销售网站完整开发指南
2026/10/14 4:11:31 网站建设 项目流程

每次有人拿着“基于Python的网上产品销售网站”这种题目来跟我聊,我第一反应就是想问一句:你想要的是能交差的演示品,还是真能放上线跑的系统?这两个目标看起来差不多,实际做起来差别极大。Python做电商网站这个方向确实经久不衰,课程设计里年年有,个人项目里想做完整业务闭环的人也常选它,但网上随便一搜的大把开源代码,大部分只是“能打开、能列商品、能加购物车”的壳子,库存怎么不超卖、订单状态怎么流转、支付回调怎么处理、部署之后静态文件为什么全挂,这些真正影响可用性的细节反而没多少人讲。

这篇文章我会把这个项目的设计思路和实现路径完整捋一遍,适合正在做课程设计或毕业设计的同学,也适合学了Python想用完整项目练手的新人。我会从需求拆解讲到数据库表设计,从前台下单链路讲到后台管理,最后聊一聊部署上线时那些你必须提前知道的坑。

1. 动手前先拆需求:这个网上销售项目到底要做什么

1.1 同一个标题,背后是两条完全不同的路

“网上产品销售网站”这个题目,能做浅也能做深。浅的版本:商品列表、商品详情、购物车、登录注册、下单,然后假装支付成功,所有数据都能存进数据库就行。深的版本:要处理库存并发、订单状态机、后台商品上下架、支付回调对账、部署到服务器之后真实可访问。

我建议所有拿到这个题的人,先做一次需求分级。

必须有的核心功能:

  • 商品模块:分类、列表、详情、搜索、上下架
  • 用户模块:注册、登录、个人信息
  • 交易模块:购物车、订单、支付、订单状态
  • 后台模块:商品管理、订单管理、用户管理

可以往后放的功能:

  • 商品规格(颜色、尺码、版本)
  • 优惠券、满减、会员价
  • 评价、收藏、浏览历史
  • 物流跟踪、售后流程
  • 数据统计报表

我见过不少人一上来就纠结评论功能怎么做、轮播图要不要加,结果核心的交易链路都没走通。项目不是功能越多越好,而是关键路径全都闭环才行。

1.2 角色和主流程:把用户故事画出来

做设计阶段,不需要写复杂文档,但至少要把两条主流程画清楚。

买家这条线是:注册登录 → 浏览/搜索商品 → 进入详情 → 加入购物车 → 提交订单 → 完成支付 → 查看订单状态。

管理员这条线是:登录后台 → 管理商品(新增、编辑、上下架) → 处理订单(发货、取消) → 查看用户列表。

你会发现,所有功能都围绕这三类核心数据转:商品、用户、订单。所以数据库建模阶段把这三块弄扎实,后面写代码会顺很多。我每次设计这类系统,都是先拿一张纸把这三块表和它们的关系画出来,画完才动手建项目。

1.3 技术栈的边界:这一版做到哪里算完

还有一件事值得提前说清楚:技术选型不是越潮越好。这个题目的核心是业务逻辑,不是框架炫技。我用过一堆花哨的方案,最后回头发现,稳定、常见、好部署才是第一位的。选一套主流组合,出了问题搜得到答案,这才是最实在的。

2. 技术选型:为什么Python配Django是这类项目的最稳组合

2.1 Python做电商网站,优势其实在业务层

很多人说Python慢,做电商不合适,这话不能说全错,但要分场景。对中小型的网上销售网站来说,瓶颈从来不在语言的执行速度,而在开发效率和业务迭代速度。Python语法简练,Django自带ORM、模板引擎、表单处理、后台管理、认证系统,一个人两三周就能把一个完整项目搭起来。换成Java那一套,同样规模的项目准备工作就多不少。

电商网站本质上是数据和流程密集型的系统:商品要存,价格要算,订单要流转,库存要扣。Python的ORM和数据处理能力正好匹配这个需求。你不必自己写复杂的JDBC,也不用纠结手动拼SQL,模型定义好之后,查询、关联、事务都有一套成熟方案。

2.2 Django与Flask的取舍:选错了后面会补课

这是拿到题目之后第一个需要做的选择题。我用一张表把关键差异列出来。

对比维度DjangoFlask
上手体验全家桶一步到位,概念较多轻量起步,hello world很快
ORM与数据库迁移自带ORM和migration体系需要自己装SQLAlchemy并整合
后台管理自带Admin,零代码可用需开发或用第三方扩展
表单与校验自带Form体系需自己处理
适合场景功能完整的业务系统轻量API、前后端分离项目
学习成本前期陡峭,后面省心前期平缓,后期补功课

我的建议很明确:如果项目要求的是完整网站形态,包括后台、登录、表单提交这些页面,直接选Django。Flask不是不行,但你在做完功能之后会发现有一堆通用事情(CSRF保护、数据库迁移、Admin后台)都是后来才想起来补的,平白多花时间。这不是说Flask不好,而是在这个题目里,Django的出场成本更低。

2.3 除了Web框架,这套技术栈还需要什么

  • 数据库:MySQL,业务数据都在这里;开发阶段可以先跑SQLite,部署时切MySQL
  • 缓存/会话:Redis,用于首页缓存、验证码、并发锁场景(量不大时可以不引入)
  • 前端:模板渲染为主,配合少量JavaScript;不要一上来就上前后端分离,那会让这个项目的复杂度翻倍
  • 部署:Nginx + Gunicorn,云服务器一台就够

技术栈越少越好。能用框架自带的能力解决,就不要自己造轮子。

3. 数据库设计:商品、用户、订单三张核心表撑起整个系统

3.1 商品设计:先搞清楚SPU和SKU的区别

如果你只是卖“一本书”“一个杯子”这种没有规格差异的商品,一张商品表就够。但稍微想远一点,卖个T恤有颜色、尺码,卖个手机有存储版本,这时候商品就不是单一实体了,而应该拆成两层:SPU(商品)和SKU(具体售卖单元)。

SPU是“这个东西叫什么”,SKU是“哪一款能买”。比如“某款运动T恤”是SPU,“白色/M码/ 79元/ 库存200件”是SKU。详情页展示SPU信息,选择和下单时对应到SKU。

Django模型可以这样写:

class Category(models.Model): name = models.CharField(max_length=100) parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL) class Product(models.Model): name = models.CharField(max_length=200) category = models.ForeignKey(Category, null=True, on_delete=models.SET_NULL) description = models.TextField(blank=True) main_image = models.ImageField(upload_to="products/", blank=True) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) class SKU(models.Model): product = models.ForeignKey(Product, related_name="skus", on_delete=models.CASCADE) spec = models.CharField(max_length=100, help_text="例如:白色 / M码") price = models.DecimalField(max_digits=10, decimal_places=2) stock = models.IntegerField(default=0)

第一次做项目的人往往觉得这是过度设计,但等到需要给商品加规格的时候,再回头改表结构,牵涉的代码面就很广了。宁可一开始多建一张表,也不要等到后面返工。

3.2 用户模型:别在自建登录上浪费时间

Django自带的用户认证系统已经处理好了注册、登录、会话、密码加密这些事,不要重复实现。重复造轮子的典型后果是什么?密码用MD5直接存明文,登录态管理混乱,密码重置没人做。这些都是安全硬伤。

如果要加手机号、头像、收货地址这类用户信息,有两种常规思路:一是直接用AbstractUser扩展用户表,二是建一个独立的Profile表与User一对一关联。我倾向后者,因为Django的User模型在很多内部逻辑里被引用,扩展字段尽量不往上面堆。

3.3 订单表与订单明细表:金额必须做快照

订单是这个系统里最重要的数据,设计上两个原则必须遵守。

第一,订单金额不能下单后去商品表现算。商品改价很常见,但你三个月前的订单不能因为现在商品涨了价就金额变了。所以下单那一刻的单价、总价都要存进订单表。

第二,订单明细里要冗余商品名称和规格描述。商品以后可能改名、下架、删除,但订单历史记录必须保持原样,否则售后查单时看到的全是“商品已不存在”,那体验就太差了。

class Order(models.Model): order_no = models.CharField(max_length=32, unique=True) user = models.ForeignKey(User, on_delete=models.PROTECT) total_amount = models.DecimalField(max_digits=10, decimal_places=2) status = models.IntegerField(default=0) receiver_name = models.CharField(max_length=50) receiver_phone = models.CharField(max_length=20) address = models.TextField() created_at = models.DateTimeField(auto_now_add=True) class OrderItem(models.Model): order = models.ForeignKey(Order, related_name="items", on_delete=models.CASCADE) product_name = models.CharField(max_length=200) sku_spec = models.CharField(max_length=100) price = models.DecimalField(max_digits=10, decimal_places=2) quantity = models.IntegerField()

这里有个细节要注意:价格字段用DecimalField,不要用FloatField。浮点数在计算机里是近似表示,两个浮点数做加减乘除会出现0.1加0.2不等于0.3的问题。金额用浮点数存储,等到金额累计、对账的时候,精度错误会让你怀疑人生。钱的场景一律用定点小数。

3.4 订单状态用整数枚举,别用字符串满天飞

订单状态建议用整数常量表示,然后在代码里定义成类常量。比如:

class OrderStatus: PENDING_PAYMENT = 0 # 待支付 PAID = 1 # 已支付 SHIPPED = 2 # 已发货 COMPLETED = 3 # 已完成 CANCELLED = 4 # 已取消

为什么不用字符串?一是整数在数据库里占空间小,二是防止“已支付”“支付完成”“已完成支付”这种同义不同名的脏数据。用了整数枚举,状态流转时候的判断逻辑也好写。另外,每个状态的切换条件要明确:待支付可以取消,已支付可以发货,已发货可以完成。这些规则后面做后台操作时会反复用到。

4. 商品浏览到下单支付:前台链路的实现思路与关键坑

4.1 商品列表与搜索:分页、过滤、排序是基本功

前台第一屏就是商品列表,这里最容易做粗糙的地方有三个:分页、分类过滤、搜索。

分页不能直接在视图里用列表切片硬算页码,Django自带Paginator,用起来很方便:

from django.core.paginator import Paginator from django.shortcuts import render def product_list(request): products = Product.objects.filter(is_active=True) keyword = request.GET.get("keyword", "") category_id = request.GET.get("category", "") sort = request.GET.get("sort", "new") if keyword: products = products.filter(name__icontains=keyword) if category_id: products = products.filter(category_id=category_id) if sort == "price_asc": products = products.order_by("price") elif sort == "price_desc": products = products.order_by("-price") else: products = products.order_by("-created_at") paginator = Paginator(products, 12) page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) return render(request, "products/list.html", {"page_obj": page_obj})

这里的逻辑就是把用户选择的条件一项项叠加到查询集上,最后统一分页。注意Paginator拿到不存在的页码时会抛异常,但get_page()方法会自动处理成返回最后一页,体验更友好。

4.2 购物车:数据库方案还是Cookie方案,第一版怎么选

购物车有两种常见实现路径。

一种是游客购物车,用Cookie或localStorage存SKU和数量,好处是用户不登录也能加购,坏处是换设备数据丢失,后端无法干预。另一种是登录购物车,用数据库表存,用户登录后才能用,后台可以管理和统计。在这个项目里,我建议第一版直接做数据库购物车。理由很简单:实现简单,逻辑直观,而且电商网站下单本来就必须登录。

class CartItem(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) sku = models.ForeignKey(SKU, on_delete=models.CASCADE) quantity = models.IntegerField(default=1) class Meta: unique_together = ("user", "sku")

购物车页面做的事情很简单:遍历当前用户的CartItem,关联SKU和Product,算出每行小计和总价。加购接口要注意两点:一是同一个SKU被重复加入时应该更新数量而不是新增记录,这靠unique_together约束实现;二是加入购物车时要检查SKU是否处于可售状态(is_active),下架商品不能让用户加入。

4.3 下单与库存扣减:这是整个项目最核心的并发坑

购物车里挂着商品,用户提交订单,最危险的一步来了:库存扣减。

很多初版代码是这么写的:先读库存,判断库存够不够,够了就扣,不够就提示。这套逻辑在单用户单进程时没问题,但只要两个人同时买最后一个商品,两个请求都可能读到库存为1,然后都判断“够”,都扣成0,结果超卖。

正确做法是使用事务和行级锁,把“读库存判断 + 扣减”变成一个原子操作:

from django.db import transaction @transaction.atomic def create_order(user, sku_id, quantity): sku = SKU.objects.select_for_update().get(id=sku_id) if sku.stock < quantity: raise ValueError("库存不足") sku.stock -= quantity sku.save() # 创建订单和订单明细的记录……

select_for_update()会在数据库层面给这一行加排他锁,直到事务提交才释放。第二个请求过来时,如果第一个请求还没提交,它只能等待;等锁释放后,它读到的库存已经是扣减后的值,这时候自然就发现不够了。

这个坑我第一次做的时候根本没意识到,直到用两个浏览器同时下单测试才暴露。后来想想,电商系统的技术难点基本都集中在“同时发生”的事件上,想明白这一点,你就知道为什么一定要用事务和锁了。

4.4 支付模块:模拟支付和真实支付各怎么做

课程设计阶段,支付可以做一个模拟收银台:用户提交订单后跳到支付页面,页面展示订单号和应付金额,点“确认支付”后把订单状态改成已支付。这个流程虽然不产生真实资金,但把支付前后状态变化的体验完整模拟出来了。

如果未来要接真实支付,流程会复杂一些,但核心逻辑也就三步:

  1. 用户下单后,后端调用第三方支付平台的统一下单接口,拿到支付链接或二维码
  2. 用户付款成功后,支付平台向你配置的回调地址发送通知
  3. 后端收到回调,校验订单号和金额,确保和下单时一致,再把订单状态置为已支付

这里有个关键经验:订单状态不能只靠前端跳转页返回就改。前端跳转可以伪造,而且支付平台支付成功后一定会回调,要把回调当作状态更新的唯一可靠来源,同时对重复回调做幂等处理。

5. 后台管理和订单流转:别让管理端变成摆设

5.1 Django Admin直接用,还是有选择地改造

很多教程喜欢教人从零写一套后台管理系统,包括登录页、侧边栏、数据表格、表单页,工作量相当大。但如果你用的是Django,框架自带的Admin已经能覆盖这个项目80%的管理需求。

我的建议分两种情况。

课程设计和毕设,直接注册模型到Admin,然后配置好显示字段、筛选条件、搜索字段,界面虽然朴素但功能齐全,导师演示完全够用。

如果是想上线给别人用,Admin也可以作为第一版的管理后台先用着,等业务流程稳定后再考虑基于Vue或者模板写一套更美观的定制后台。后台系统的核心是数据操作,不是视觉效果,先把业务跑通更重要。

5.2 把Admin配置到“能用顺手”的程度

只注册模型是不够的,默认的列表页不显示关键字段,根本没法管理。至少要配置列表字段、筛选器、搜索框,还有细节页里哪些字段可编辑。

@admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display = ["name", "category", "is_active", "created_at"] list_filter = ["is_active", "category"] search_fields = ["name"] @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ["order_no", "user", "total_amount", "status", "created_at"] list_filter = ["status"] readonly_fields = ["order_no", "total_amount", "created_at"]

一对多管理也是个常用技巧。在SKU管理页里,希望直接看到对应商品、编辑价格和库存,可以用InlineModelAdmin把SKU嵌套在Product页面里管理,这样一眼就能看到所有规格。

5.3 后台操作权限:普通用户和管理员绝对不能混在一起

Django自带auth框架,里面已经实现了用户、组、权限的完整体系。后台注册的模型,默认就支持按模型粒度设置增删改查权限。

你需要做的事情其实很简单:给管理员账号一个超级用户身份,普通注册用户只保留前台使用权限。后台入口一般用原有的/admin就能用,但如果你改了后台地址,也要注意不能把修改后的路径暴露在前台页面上,避免让人直接猜到管理入口。

5.4 订单操作的完整状态流转

后台处理订单的核心就是状态的合法流转。待支付订单可以被买家取消;已支付订单,管理员可以标记发货;已发货订单,买家确认收货后变成已完成;已取消订单不可再支付和发货。

if order.status == OrderStatus.PAID and action == "ship": order.status = OrderStatus.SHIPPED order.save()

我的经验是,所有状态修改操作都集中在后台统一处理,前台只能提交指令,不要直接改状态字段。这样出了问题好排查,也方便以后加操作日志。

6. 部署上线与踩坑检查:把网站真正跑起来

6.1 部署方案:不用太复杂,够稳就够

本地开发环境通常是python manage.py runserver,但这个命令只能用于开发调试,不能用来对外提供服务。实际部署建议用这套常见组合:Gunicorn启动Django应用,Nginx做反向代理和静态文件服务,MySQL存数据,Redis按需引入做缓存。

核心部署步骤大致是这样:

  1. 服务器上创建Python虚拟环境,安装requirements.txt
  2. Django的settings.py里把DEBUG设为False,填上服务器的域名或公网IP
  3. 执行python manage.py migrate迁移数据库,执行collectstatic收集静态文件
  4. 用Gunicorn启动服务:gunicorn mysite.wsgi:application --bind 127.0.0.1:8000
  5. 配置Nginx把80端口请求转发到8000,同时为静态文件和媒体文件设置别名
  6. 用systemd守护Gunicorn进程,保证重启服务器后服务能自动拉起

6.2 静态文件和上传图片为什么会全挂

这是部署阶段最高频的问题,没有之一。本地运行时,Django开发服务器会帮忙处理静态文件;但一旦进入生产模式(DEBUG=False),Django默认不再托管静态文件,你必须先执行collectstatic把所有静态资源收集到一个目录,然后在Nginx里指向这个目录。

上传的图片属于媒体文件,Nginx同样要单独配置:

location /static/ { alias /var/www/mysite/static/; } location /media/ { alias /var/www/mysite/media/; }

这两个location缺一不可。只配置其中一个,就会出现页面打开正常但图片全裂的情况。我见过很多人部署完网站,首页文字正常,商品图片全挂,折腾半天最后发现就是media目录没配。

6.3 上线后常见问题自查清单

现象常见原因处理方式
打开域名显示500ALLOWED_HOSTS未包含该域名或IP在settings里添加
页面能开,图片CSS全无静态文件未collect或Nginx未配置执行collectstatic并检查配置
提交表单报CSRF错误模板缺少csrf_token在form里加{% csrf_token %}
时间显示与本地差8小时USE_TZ设置导致TIME_ZONE = "Asia/Shanghai"
修改代码不生效Gunicorn没重启重启systemd服务
首页加载很慢大列表未分页或未加索引列表页分页,常用查询字段加索引

6.4 上线前最后检查的几件事

部署完成不代表交付结束。我每次上线前都会过一遍这几项:DEBUG必须为False,SECRET_KEY不能出现在公开仓库里,数据库要有备份方案,后台管理员账号必须用强密码。本项目的数据库里存着用户密码哈希和订单数据,这个底线必须守住。

另外,迁移和collectstatic操作要在部署之前执行到位,上线后再发现数据库缺字段或静态文件缺失,就会影响真实用户了。

做这个项目的过程中,我最大的体会是:网上销售网站看着功能简单,真正做起来每一个环节都有值得抠细节的地方。设计阶段花时间把表结构和状态流转想透,实现阶段严格按流程来,部署阶段把静态文件和进程守护理顺,项目从头到尾会顺畅很多。如果你也正在做这个题目,我给你的建议是从数据库建模开始动手,先把商品、用户、订单这三块地基打好,后面加页面、加逻辑都只是往骨架上填肉。

我最后还想再补一句:选择Python做这类项目,最大的优势不是语言本身,而是它能让你把有限的时间花在业务逻辑的思考上,而不是跟底层细节纠缠。趁这个机会把电商系统的数据模型和交易流程吃透,以后做再复杂的后端项目,很多思路都是通用的。祝你能把这条链路完整走一遍,收获会比看十篇教程都大。

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

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

立即咨询