ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django水果销售系统全解析:从数据模型到部署的一站式实战指南

Django水果销售系统全解析:从数据模型到部署的一站式实战指南 简介基于Python、Django和MySQL的在线水果销售系统毕业设计项目内含完整源码、数据库脚本与演示视频适合计算机相关专业学生作为毕业设计参考也可用于Django电商项目实战练习。系统在功能结构上划分为系统登录、用户管理、水果管理、订单管理四大核心模块登录模块支持管理员、买家和卖家账号区分采用用户名密码加短信验证码双重校验用户管理模块可新建账号并分配相应权限水果管理模块支持类别维护、信息更新和热门水果首页推荐订单管理模块覆盖在线下单、支付、状态跟踪与评价反馈完整串联前台购物和后台管理流程。前台与后台界面分离普通用户可完成注册登录、水果浏览、购物车、下单支付等一系列操作管理员则能集中管理用户、类别、水果、订单和评价数据便于理解电商系统的权限划分与数据流转。资源包共782个文件约42.57MB主要涵盖Python源码、HTML/CSS/JS页面、Vue组件、SQL脚本、静态图片和演示视频并提供安装与启动脚本可快速在本地部署运行目录结构清晰便于按模块阅读和维护。目前已有132人浏览学习通过该项目可快速掌握Django MTV架构、数据库关系设计和前后端交互等要点具备较强的二次开发与参考价值。1. 为什么水果销售系统比你想的更值得拆一遍一个水果销售系统被贴上“毕业设计”标签后很容易让人以为是增删改查的堆砌。但真正把前台购物车、后台推荐位、在线支付、订单状态流转串起来时你会发现它其实是一个典型的“状态机 权限边界”项目同一批水果数据在用户端有上架/下架、推荐/不推荐、有库存/无库存的区别订单从提交、支付、待发货到完成、评价每一步都涉及数据一致性和接口幂等性。这个基于 Python Django 的在线水果销售系统源码包包含完整的前台用户端和后台管理端外加 MySQL 数据库脚本与演示视频正好覆盖了这些容易在面试和答辩中被追问的细节。适合两类人一是正在选毕业设计课题的学生想找个业务闭环完整、不用纯抄书本的 Django 项目二是工作中只写过 API 或只写过管理后台的开发者想看看“前台 后台 支付回调”在一个单体项目里如何组织。接下来我会从数据模型设计讲起然后落到购物流程、后台推荐位机制最后用源码包里的 .bak 文件线索聊聊备份与恢复的实战注意点。2. 数据库模型设计先把水果、用户、订单的关系理清楚一个看似简单的“水果销售系统”如果把表关系建错后面写购物车和订单会非常痛苦。Django 的 ORM 虽然能自动建表但表之间的 ForeignKey、ManyToManyField 以及状态字段取值必须先在 ER 层面想清楚。这套系统的核心实体是用户、水果类别、水果信息、购物车、订单、支付记录和评价它们之间不是简单的“一对多”而是围绕“订单”形成了一条完整的状态链。2.1 用户与角色的拆分方式网上水果销售系统分为前台和后台但后台又区分管理员和注册用户的管理权限。Django 自带的 User 模型可以扩展这里更常见的做法是扩展一个 Profile 表用 user_type 字段区分角色而不是新建多张用户表。from django.contrib.auth.models import AbstractUser class UserProfile(AbstractUser): USER_TYPE ( (1, 管理员), (2, 买家), (3, 卖家), ) user_type models.SmallIntegerField(choicesUSER_TYPE, default2) phone models.CharField(max_length11, blankTrue) # 收货信息可以冗余在订单里也可以单独建表说明这里直接继承 AbstractUser便于复用 Django 自带的认证和密码哈希逻辑。user_type 用于登录后的权限判断例如前台用户只能操作自己的购物车而管理员可以访问 /admin 或自定义后台。phone 字段用于演示短信验证码登录源码包中验证码部分通常会用 Django Cache 或 Session 暂时存储避免引入 Redis 额外依赖。数据库层面MySQL 中对应user_profile表额外字段会被映射成实际列。如果你用 Django 的makemigrations生成迁移文件注意 AbstractUser 扩展后第一次 migrate 前要先把数据库里已有的 django_user 表清掉否则会因为关联冲突报错。2.2 水果、类别与推荐位的状态设计水果信息表不能只有名称和价格必须包含上下架状态、推荐状态和库存量。否则前台无法区分“这个水果为什么不显示”或“为什么加购时提示库存不足”。字段名类型说明idint主键categoryForeignKey关联水果类别表namevarchar水果名称pricedecimal(10,2)单价stockint剩余库存statustinyint1上架 0下架is_recommendtinyint1推荐 0不推荐salesint累计销量create_timedatetime入库时间推荐位是这套系统的功能亮点前台有“水果推荐”栏目数据由后台管理员点击“推荐”按钮控制。实现上不需要额外建推荐表直接在水果信息表加一个布尔字段前台只取status1 and is_recommend1的前 N 条即可。这样做的好处是推荐操作变成一个普通 UPDATE不需要维护独立的推荐记录表坏处是只能做简单推荐位无法配置推荐顺序和过期时间。如果实际项目要扩展可以再加一个 recommend_order 字段来调整顺序。class Fruit(models.Model): category models.ForeignKey(Category, on_deletemodels.CASCADE) name models.CharField(max_length100) price models.DecimalField(max_digits10, decimal_places2) stock models.IntegerField(default0) status models.BooleanField(defaultTrue) is_recommend models.BooleanField(defaultFalse) sales models.IntegerField(default0)说明on_deletemodels.CASCADE表示删除类别时连带删除该类别下水果这在毕设场景里比较省事。但在真实项目中类别删除一般会做保护或软删除避免误删导致订单里的水果关联失效。这里我一般会建议把on_delete改成SET_NULL并把外键设置为nullTrue留作审计数据。2.3 订单表与支付状态机订单是整个系统的核心也是最容易被问“如何保证一致性”的地方。普通的小型电商系统里订单表至少要有订单号、用户、总金额、状态、收货信息。同时因为要支持在线支付和订单评价状态字段需要仔细设计。建议用 Int 类型保存而不是直接存字符串避免因为空格或大小写产生脏数据。class Order(models.Model): ORDER_STATUS ( (0, 待支付), (1, 已支付/待发货), (2, 已发货), (3, 已完成), (4, 已取消), ) order_no models.CharField(max_length32, uniqueTrue) user models.ForeignKey(UserProfile, on_deletemodels.CASCADE) total_price models.DecimalField(max_digits10, decimal_places2) status models.SmallIntegerField(choicesORDER_STATUS, default0) receiver_name models.CharField(max_length50) receiver_phone models.CharField(max_length11) receiver_address models.CharField(max_length200) create_time models.DateTimeField(auto_now_addTrue)说明order_no 是业务主键因为 MySQL 自增 id 不适合直接暴露给用户作为订单号也不方便做支付回调时的幂等判断。生成订单号常见做法是时间戳 用户id 随机数保证唯一性。支付记录表通常单独建表关联订单号、支付平台流水号、支付金额、回调状态和时间这样订单表只关心业务状态支付表只关心资金结果两边的接口通过 order_no 关联。这里有一个容易被忽视的坑在线支付回调是异步的回调里首先用订单号和金额去查支付记录如果发现已经处理过就直接返回成功避免重复回调导致订单状态被覆盖。在毕设阶段你不需要真的接微信支付或支付宝但项目里通常会模拟一个支付页面点击“在线支付”后直接生成支付记录并把订单状态从 0 改为 1。源码包里会用 Django View 写一个模拟支付接口演示视频里也能看到这个流程。3. 前台功能实现从注册登录到购物车和下单前台页面基于 Vue 和 Django 的混合模式开发源码里能看到.vue.bak文件比如IndexMain.vue.bak、IndexHeader.vue.bak。这说明前端用了 Vue 组件化而后端提供 JSON API。这种前后端不分离的毕设项目通常做法是把 Vue 构建后的 dist 目录直接放到 Django 的静态目录下或者用 Django 模板渲染页面骨架再由 Vue 接管局部交互。这里我建议你关注业务逻辑本身而不是纠结是否前后端分离。3.1 注册登录与验证码用户注册需要校验用户名唯一性、密码强度、手机号格式。短信验证码在毕设里一般不会真的发短信而是后端生成一个随机数打印到日志或返回给前端前端自动填充或手动查看。Django 的 Session 是临时存储验证码的常用方案。import random from django.views.decorators.http import require_POST from django.http import JsonResponse require_POST def send_sms_code(request): phone request.POST.get(phone) code str(random.randint(100000, 999999)) request.session[sms_code] code request.session[sms_phone] phone # 模拟发送短信实际开发可对接第三方短信接口 return JsonResponse({code: 0, msg: 验证码已发送, debug_code: code})说明把验证码放到 Session 里意味着验证码跟具体用户会话绑定30 分钟或更短时间过期。调试时直接返回 debug_code方便前端联调。实际项目中必须把 debug_code 去掉并且加发送频率限制比如同一手机号一分钟内不能重复发送。登录接口要检查用户名/手机号 密码 短信验证码验证码正确性优先于密码校验可以防止被批量撞库。注册登录页面还涉及 Vue 组件的数据绑定例如IndexMain.vue.bak里可能有登录弹窗逻辑表单提交时调用fetch或axios发送 POST 请求。如果前端和后端在同一域名下可以直接用 Cookie 带 Session如果你把前端单独跑在 8080 端口就需要后端启用 CORS并在请求头里加上X-CSRFToken否则 Django 默认的 CSRF 校验会拦截所有非 GET 请求。3.2 购物车如何与登录状态绑定购物车数据不需要单独建一张永久表存在 Redis 或 Session 里都可以。但为了毕业设计答辩时展示“数据落库”常见做法是建一张 cart_item 表用户未登录时不允许加购登录后把商品 id、数量、价格快照写到数据库。价格快照的意思是加购时把当时的水果单价也存进去之后水果改价不会影响这个购物车条目。class CartItem(models.Model): user models.ForeignKey(UserProfile, on_deletemodels.CASCADE) fruit models.ForeignKey(Fruit, on_deletemodels.CASCADE) quantity models.IntegerField(default1) price models.DecimalField(max_digits10, decimal_places2) checked models.BooleanField(defaultTrue) create_time models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, fruit)说明unique_together保证同一个用户同一个水果只能有一条购物车记录再次加购时只更新 quantity避免数据重复。前端展示购物车时需要联查水果表获取最新图片和名称但价格用购物车里的快照这样总价以用户加购时为准。前端“加入购物车”按钮通常放在水果详情或列表页未登录时点击会跳转到登录弹窗。3.3 下单时的事务与库存扣减从购物车生成订单是一个多步操作创建订单、创建订单明细订单里的每个水果、扣减库存、清空对应购物车条目。这四步必须放在同一个数据库事务里任何一步失败都要回滚。Django 的transaction.atomic()可以轻松实现。from django.db import transaction def create_order(request): with transaction.atomic(): user request.user cart_items CartItem.objects.filter(useruser, checkedTrue) if not cart_items.exists(): return JsonResponse({code: 1, msg: 请选择要结算的商品}) total 0 for item in cart_items: fruit Fruit.objects.select_for_update().get(pkitem.fruit_id) if fruit.stock item.quantity: return JsonResponse({code: 1, msg: f{fruit.name} 库存不足}) fruit.stock - item.quantity fruit.sales item.quantity fruit.save() total item.price * item.quantity order Order.objects.create(...) # 生成订单头和明细 cart_items.delete()说明select_for_update()是悲观锁在事务内锁定这些水果行防止同时下单时库存扣成负数。这在并发量不高的毕设项目里足够用但如果只是展示也可以不加锁直接 UPDATE 时加条件stock num来判断是否成功。注意一旦在with transaction.atomic()里调用了return JsonResponse其实事务会正常提交这是因为 return 不会抛异常。但更好的做法是先校验所有库存最后统一创建避免事务中间 return 导致逻辑混乱。支付环节在源码包里通常是模拟的用户点击“在线支付”进入一个确认页面调一个pay_order接口接口里把订单状态改成“已支付”同时生成支付记录。这个接口必须做幂等控制用filter(order_noorder_no, status0).update(status1)这样的条件更新返回受影响的行数如果行数为 0 说明订单已经支付过或不存在避免重复操作。4. 后台管理权限划分、推荐位与订单状态流转后台系统包含了管理员管理、用户管理、水果类别管理、水果信息管理、订单管理、支付管理和评价查看。这里最有讨论价值的是三个点一是 Django 自带的 admin 如何二次改造二是水果推荐位如何设计接口三是订单状态在后台如何流转。4.1 admin 二次改造还是手动后台源码里的后台页面没有完全使用 Django admin 默认样式而是自己写了一套 Vue 页面。这意味着后台的权限控制不能只依赖 Django admin 的is_staff而是要在视图函数里手动做装饰器或中间件判断。from functools import wraps from django.http import JsonResponse def admin_required(view_func): wraps(view_func) def wrapper(request, *args, **kwargs): if request.user.is_authenticated and request.user.user_type 1: return view_func(request, *args, **kwargs) return JsonResponse({code: 403, msg: 无权限访问}) return wrapper说明这个装饰器可以用在管理员相关的视图上也可以在 Django 中间件里统一拦截/admin/开头的 URL。注意request.user是 UserProfile 实例所以可以点出user_type。如果你希望代码更简洁可以直接用 Django admin 的has_module_permission和 Group 权限但那样就失去了前端页面的展示效果也不方便演示。4.2 推荐位机制与列表缓存后台的水果信息管理页面应该支持按类别筛选、按名称搜索、改库存价格、状态切换以及一键推荐。推荐按钮的前端逻辑很简单调一个 POST 接口把is_recommend置为 1 或 0。但需要注意一旦前台推荐列表频繁被访问每次都查数据库会浪费性能。毕设项目可以用 Django 的cached_property或者简单地把推荐数据放在cache()里。from django.core.cache import cache def get_recommend_fruits(): fruits cache.get(recommend_fruits) if fruits is not None: return fruits fruits list(Fruit.objects.filter(status1, is_recommend1)[:8]) cache.set(recommend_fruits, fruits, 60 * 5) return fruits说明这里有一个常见的坑——当后台管理员把某个水果取消推荐后缓存里可能还保存着旧数据最长要 5 分钟才刷新。所以更新推荐状态的接口里要主动cache.delete(recommend_fruits)或者直接用 Django 的cache_page装饰器并配合信号量失效。在源码包的演示视频里推荐位通常刷新后立即生效就是因为后台接口里做了缓存清理。另外后台列表页的“推荐”按钮通过 AJAX 提交不能在 HTML 里直接写 form submit否则整个页面会刷新。前端框架通常会在表格的“操作”列渲染两个按钮推荐/取消推荐点击后发 POST 请求成功后用Vue.set或重新拉取列表来更新当前行数据。源码包里的IndexAsideStatic.vue.bak是侧边栏组件IndexHeader.vue.bak是顶栏这些都说明后台布局是典型的“侧边栏菜单 顶栏 内容区”结构导航菜单由路由控制。4.3 订单状态流转与权限边界后台订单管理需要实现的状态流转有待支付订单可以关闭或由用户取消已支付订单进入待发货管理员点击发货变成已发货用户确认收货后变成已完成已完成订单可以查看评价。这个流程看起来像简单的 UPDATE status但如果在同一张订单表上直接改很容易造成状态乱跳比如从“已发货”直接回到“待支付”。解决办法是给订单表加一个order_status_log记录表或者至少写一个专门的状态更新接口接口里校验“当前状态是否允许转移到目标状态”。ALLOWED_TRANSITIONS { 0: [1, 4], # 待支付 - 已支付/取消 1: [2], # 已支付 - 已发货 2: [3], # 已发货 - 已完成 } def change_order_status(request, order_no, target_status): order Order.objects.get(order_noorder_no) if target_status not in ALLOWED_TRANSITIONS.get(order.status, []): return JsonResponse({code: 1, msg: 非法状态流转}) order.status target_status order.save()说明这个表就是状态机转移表。在实际项目里你还可以在迁移表里加一个update_time字段并用信号或重写save()方法来自动记录修改前后的状态。这样做的好处是以后运营或客服提问“这个订单怎么突然变成已完成了”时你有一张日志表可以审计。评价功能通常放在订单完成后。用户只能对已完成订单评价评价内容关联水果表和订单号。后台查看评价时按订单维度展示不需要单独的“评价管理”页面但如果你想做成一个可以删除不当评价的后台功能也很简单在评价模型上加一个is_visible字段前台只查is_visibleTrue的评价。5. 从 .bak 文件到一键运行备份恢复与部署排错源码包里出现了大量.bak文件比如index.html.bak、update-password.vue.bak、Install.bat、2-run.bat、3-build.bat这其实是开发过程中留下的备份或临时文件。很多初学者看到一个.bak文件就不知所措或者直接忽略但实际上这些文件能透露项目的运行方式和排错线索。5.1 .bak 文件到底是什么要不要保留.bak文件通常是编辑器或开发者手动备份产生的。比如你修改了index.html感觉可能改坏就复制了一份index.html.bak或者你用 VS Code 开启了“文件自动保存”后插件把旧版本重命名成了.bak。这类文件的特征是内容跟源文件几乎一致但不会参与项目的编译和运行。Python 项目里.py.bak文件不会被执行Vue 项目里.vue.bak文件不会被打包进构建产物。但有一个例外如果项目根目录里没有index.html而只有index.html.bak那说明你需要先把.bak重命名回index.html才能启动。检查方法很简单在项目根目录执行dirWindows或ls -laLinux看是否有同名的非.bak文件。如果确实缺失用这条命令恢复cp index.html.bak index.html或 Windows 下copy index.html.bak index.html在 Django 项目中模板文件如果本身是.html却被写成了.html.bakDjango 的模板加载器不会识别它访问页面时会报 TemplateDoesNotExist 错误。所以遇到.bak文件时第一反应是“它是不是被误改了扩展名”而不是盲目删除。5.2 一键启动脚本里的环境坑源码包里的.bat文件说明作者是在 Windows 下开发的。Install.bat通常做三件事创建虚拟环境、安装 requirements.txt、初始化数据库。2-run.bat一般是启动 Django 开发服务器3-build.bat可能是构建前端文件。我建议不要直接双击先打开看内容确认 Python 路径和虚拟环境激活方式。echo off python -m venv venv call venv\Scripts\activate.bat pip install -r requirements.txt python manage.py makemigrations python manage.py migrate python manage.py runserver 127.0.0.1:8000这段脚本的问题是python命令不一定指向 Python 3.x如果你的电脑装了多个 Python 版本可能会启动错误的环境。更稳妥的写法是使用py -3或者指定完整路径。另外第一次运行后如果 MySQL 密码不对migrate会直接报错此时要检查项目里的settings.py数据库配置包括数据库名、用户名、密码、端口。最常见的问题是 MySQL 8 默认的认证插件是caching_sha2_password而 Django 老版本可能不支持需要在 MySQL 里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;注意这个命令会改变 root 用户的认证方式仅建议在本地开发环境使用。如果你的 MySQL 版本较新Django 版本也较新则不需要这一步。也可以通过配置文件里的OPTIONS指定认证插件但那条 SQL 是最直观的解决办法。5.3 前端构建后如何与 Django 整合如果项目是 Vue 开发的前台那么开发阶段一般用npm run serve启动热更新但最终部署要把前端构建成静态文件交给 Django 托管。3-build.bat通常包含这些命令npm install npm run build构建后会在dist/目录生成index.html和静态资源。你需要在 Django 的settings.py里做两件事一是配置静态文件目录二是为前端路由添加兜底视图。STATICFILES_DIRS [BASE_DIR / dist, BASE_DIR / static]然后在urls.py里加一条兜底路由确保用户访问/或前端路由时返回index.htmlfrom django.views.generic import TemplateView urlpatterns [ re_path(r^.*$, TemplateView.as_view(template_nameindex.html)), ]这里有一个隐患兜底路由会拦截你所有未匹配的后端 API所以必须把re_path放在所有path路由之后否则/api/login会被当成前端路由返回 HTML。如果你的前端项目用的是 history 模式路由刷新页面时如果 Django 没有兜底会出现“404 Not Found”加了这个兜底就能直接打开首页。如果前端用的是 hash 模式URL 里有#则不需要这个兜底因为#后面的内容不会发送到服务器。5.4 验证项目是否完整的三个小方法部署完成后从技术复核角度我会用三个手段确认系统没有明显问题。首先是看入口文件是否存在有没有manage.py有没有被误改名成.bak的manage.py.bak。第二步是查看数据库迁移记录执行python manage.py showmigrations确认所有迁移都打勾没有未应用的迁移。第三是模拟一次完整购物流程注册用户、添加商品到购物车、提交订单、在线支付、后台发货、用户确认收货、评价。这套流程只要有一个环节报错就能很快定位是前端路由问题、后端 API 问题还是数据库关联问题。python manage.py check --deploy这个命令会做生产环境安全检查例如是否开启 DEBUG、是否配置了 ALLOWED_HOSTS、是否使用 HTTPS。虽然毕设项目不需要完全按生产标准来但执行一下能帮你发现很多隐藏的小问题比如DEBUGTrue会在浏览器里暴露详细报错信息答辩演示时万一出错最好提前关闭 DEBUG 并配置ALLOWED_HOSTS [*]。等到答辩前再改回默认不影响演示效果。本文还有配套的精品资源点击获取
返回列表