微信小程序+Django在线点餐系统毕设全解析:从需求到答辩
简介全栈开发已成为软件工程实践中的核心能力它要求开发者同时掌握前端交互、后端逻辑与数据持久化等多层技术。微信小程序作为轻量应用形态凭借其即用即走的特性已成为餐饮、零售等生活服务场景中的常见载体而Python与Django的组合则以“自带电池”的高效开发模式成为构建API服务与后台管理系统的热门选择。理解前后端如何通过RESTful API协作、如何设计符合业务逻辑的数据库模型、如何处理购物车与订单状态流转是构建可靠业务闭环的基础。此类技术在校园点餐、食堂预订、小规模餐饮门店数字化等场景中具有广泛的应用价值。本文以在线点餐系统为例系统性拆解了从需求建模、Django后端开发、小程序前端实现到前后端联调避坑的完整流程并针对答辩中常见的设计与并发问题给出实践方案为准备相关毕设或练手项目的开发者提供了一份可落地的技术参考。 毕设这个事说难不难说简单也不简单。尤其是每年都有大批人涌向“微信小程序PythonDjango”这套组合但真正能把“在线点餐”这个题目从零到一完整做出来、还能顺利通过答辩的比例其实没那么高。我帮人改过不少类似的项目最常见的问题就是代码能跑但问两句就露馅——数据库为什么会这么设计、接口为什么这么写、下单并发怎么处理完全答不上来。这篇文章就来拆解一个我自己梳理过很多遍的毕设方案前端微信小程序、后端PythonDjango的在线点餐系统。不只是给你一套能跑的代码结构而是把背后“为什么这么做”的道理一起讲清楚包括需求建模、接口设计、联调避坑、答辩准备全程按我自己做项目时的思路来说。如果你正准备做这个方向的毕设或者想自己动手做一个练手项目这篇文章应该能帮你少走不少弯路。1. 为什么毕设选“微信小程序PythonDjango做在线点餐”这条路1.1 毕设选题的现实考量毕设选题和平时自己写玩具项目不太一样它有几个硬指标工作量要够、技术栈要有辨识度、演示效果要直观、答辩时要有东西可讲。在线点餐系统恰好把这些全占了。先看工作量。一个完整点餐系统涉及用户端、商家端、管理端三个视角包含菜品浏览、购物车、下单、支付或模拟支付、订单管理、数据统计等模块无论往哪个方向展开都能凑够任务量。更关键的是它不像纯后台管理系统那样只能对着电脑看表格而是有一个真实的小程序界面现场演示的冲击力强很多。再看技术栈。微信小程序是当前应用最广的轻应用形态之一Python是入门门槛最低、生态最好的后端语言之一Django则是Python里最成熟的全栈框架。这套组合在招聘市场和毕设评委眼里都属于“正经技术栈”不会出现“你用什么冷门框架”这种尴尬追问。1.2 技术栈组合的天然优势有人会问为什么不用VueSpring Boot或者直接用uni-app一套搞定这个问题我在做选型时也认真想过。Spring Boot当然很强但对毕设来说Java体系的前置成本偏高——环境配置、依赖管理、编译部署每一项都要时间。而Django的“自带电池”特性正好适合这种快速出成果的场景自带ORM、自带Admin后台、自带认证系统甚至自带一个开发服务器你不用额外配置就能把后端跑起来。小程序端更不用纠结。微信小程序的开发语法虽然有WXML、WXSS、JS这样一套自己的东西但整体思路和Vue非常接近会Vue的人半小时就能上手。最关键的是微信提供了完善的开发者工具模拟器、真机调试、网络抓包一条龙对没有后端调试经验的学生来说极其友好。1.3 这个项目到底能锻炼什么毕设最大的意义在于把你大学四年学的东西串起来。做这个项目你会接触到前端WXML页面结构、WXSS样式、JS交互逻辑、小程序生命周期、组件通信后端Django项目结构、模型设计、视图编写、路由配置、ORM查询数据库MySQL或SQLite的表设计、一对多/多对多关系、外键约束接口联调RESTful API设计、前后端数据格式约定、鉴权方案工程化虚拟环境、依赖管理、代码结构分层、版本控制这一套走下来你的能力基本达到了一个初级全栈开发的水准。答辩老师问你“项目中遇到过什么困难”的时候你随便挑一个真实踩过的坑讲出来都比背模板强一百倍。2. 在线点餐系统的需求拆解三端、五流程、一张数据表很多人的毕设直接上来写代码写到一半发现逻辑理不清改来改去浪费时间。我习惯先把需求拆干净再动手。2.1 核心角色与业务边界在线点餐系统看似只有“点餐”两个字但参与者其实有三类顾客通过小程序浏览菜品、加购物车、下单、查看订单状态、取消订单商家接收订单、更新订单状态接单、完成、管理菜品上下架管理员管理用户、管理全部订单、查看销售统计毕设阶段不需要把每个角色都做成独立端。最常见也是最稳妥的划分是小程序端给顾客用商家后台直接用Django自带的Admin或一个简单的Web页面管理员在同一个后台分配不同权限。这里有一个我自己当年犯过的错误一上来就把权限系统做得很复杂什么RBAC、操作日志、多商户入驻结果光是画模型就花了一周。后来想通了毕设的核心是把业务闭环打通权限控制在Django的is_staff、is_superuser基础上做一层基础判断就够了。2.2 核心业务流从加购到订单完成整个系统的核心流程其实非常简单我把完整链条列一下用户打开小程序进入首页/菜单页查看菜品分类和列表点击菜品加入购物车可以修改数量、选择规格如辣度、份量确认购物车后提交订单填写备注选择取餐方式堂食/自提/外卖提交订单后生成一条待支付记录这里毕设通常用模拟支付或接入微信支付沙箱商家端看到新订单后接单状态变为制作中/配送中用户端看到状态变化最后订单完成这六个步骤既是业务闭环也是你开发时的核心主线。每个步骤都对应一个页面或一个接口你只需要把这条链路走通项目就已经完成了60%。2.3 数据库建模从Django Model反推表设计数据库设计是答辩时的高频提问点。我先给你一张我最常用的表结构清单包含了字段和关系然后说说为什么这么设计表名关键字段说明UserProfileuser(OneToOne), avatar, phone扩展Django自带的UserCategoryname, sort_order菜品分类Dishname, category(FK), price, image, desc, status菜品信息DishSpecdish(FK), name, price_delta菜品规格如大份/小份Cartuser(FK), dish(FK), spec(FK), quantity, checked购物车项Orderorder_no, user(FK), status, total_amount, remark, address订单主表OrderItemorder(FK), dish(FK), spec(FK), quantity, price订单明细Paymentorder(OneToOne), pay_type, trade_no, status支付记录设计这套模型时有几个关键决策点订单为什么要拆成主表和明细表因为一个订单包含多个菜品如果把菜品直接存成JSON字符串虽然省事但无法对订单明细做统计查询比如“哪个菜卖得最好”就查不出来。规范的关系表设计是毕设的加分项。购物车要不要落库我的建议是落库。虽然纯前端localStorage也能做购物车但落库之后可以支持多端同步、跨设备恢复更重要的是答辩时能讲出“用户数据持久化”这个设计思路。菜品规格为什么单独建表这是很多人忽略的点。如果每个菜品只有一个价格当然不需要规格表。但真实场景中“大份3元、小份-2元”很常见把规格做成独立表价格计算逻辑就清晰了。2.4 容易被问倒的细节订单状态机怎么设计状态机是答辩时老师最爱问的考点。订单状态一般这样流转待支付待付款待接单已支付等待商家确认制作中 / 配送中商家已接单已完成已取消用户取消或超时未支付已退款支付后退款毕设可简化Django里建议用整数字段常量类来管理比如在models.py里定义class Order(models.Model): class Status(models.TextChoices): PENDING_PAY pending_pay, 待支付 PENDING_CONFIRM pending_confirm, 待接单 PREPARING preparing, 制作中 DELIVERING delivering, 配送中 COMPLETED completed, 已完成 CANCELED canceled, 已取消 status models.CharField(max_length20, choicesStatus.choices, defaultStatus.PENDING_PAY)用TextChoices的好处是接口返回和数据库存储都是可读字符串前端拿到状态后直接映射中文描述不会出现数字和状态对不上的情况。答辩时你可以顺手把状态流转图画出来讲这就是一个完整的功能闭环。3. Django后端落地项目初始化、模型迁移与API开发3.1 环境准备与项目结构开始写代码之前先把环境夯实。我推荐用venv做虚拟环境Python版本选3.10或3.11Django版本选4.x或5.x都行关键是要把依赖锁定在requirements.txt里。python -m venv venv source venv/bin/activate # Windows下是 venv\Scripts\activate pip install django djangorestframework django-cors-headers pillow pip freeze requirements.txt项目结构我用的是Django标准方式加上一个apps目录来分模块order_system/ ├── manage.py ├── config/ # 项目配置 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ ├── dishes/ # 菜品模块 │ ├── orders/ # 订单模块 │ └── payments/ # 支付模块 └── media/ # 上传的图片很多人习惯把所有逻辑塞进一个app里但模块拆分会让你在答辩时更好讲“我把项目拆分成了用户、菜品、订单、支付四个模块每个模块负责独立功能”。这句话在答辩时很加分。Django默认是单应用管理要使用apps目录需要做个小的配置调整在settings.py的同级目录创建包结构然后在INSTALLED_APPS里用类似apps.users的方式注册。这里需要注意apps目录下要创建__init__.py每个子应用也要有完整的apps.py不然Django会报错找不到模块。3.2 用DRF还是原生JSON后端接口方案我有两种推荐按你的基础二选一方案一直接用Django原生JsonResponse这个方案只依赖Django本身零学习成本。视图逻辑自己解析请求参数、自己构造响应字典。适合基础一般、时间紧张的同学。方案二用Django REST FrameworkDRFDRF是Django生态最成熟的接口框架序列化器、视图集、路由、权限控制都封装好了开发效率高代码规范答辩时说起来也更体面。缺点是学习曲线稍高。我的建议是如果时间够直接上DRF。用DRF写一套标准的ModelViewSet需要不了多少代码# apps/dishes/views.py from rest_framework import viewsets from .models import Category, Dish from .serializers import CategorySerializer, DishSerializer class CategoryViewSet(viewsets.ModelViewSet): queryset Category.objects.filter(status1).order_by(sort_order) serializer_class CategorySerializer class DishViewSet(viewsets.ModelViewSet): queryset Dish.objects.filter(status1) serializer_class DishSerializer配合router注册# config/urls.py from rest_framework.routers import DefaultRouter from apps.dishes.views import CategoryViewSet, DishViewSet router DefaultRouter() router.register(rcategories, CategoryViewSet) router.register(rdishes, DishViewSet) urlpatterns [ path(api/, include(router.urls)), ]一个基础的菜品查询接口就完事了DRF甚至自动帮你生成了接口调试页面这在联调和答辩演示时都很好用。3.3 核心Models的完整实现我来把订单相关的模型完整写一遍因为这部分是项目的地基很多查询和逻辑都依赖它# apps/orders/models.py from django.db import models from django.conf import settings from apps.dishes.models import Dish, DishSpec class Order(models.Model): class Status(models.TextChoices): PENDING_PAY pending_pay, 待支付 PENDING_CONFIRM pending_confirm, 待接单 PREPARING preparing, 制作中 DELIVERING delivering, 配送中 COMPLETED completed, 已完成 CANCELED canceled, 已取消 order_no models.CharField(max_length32, uniqueTrue, verbose_name订单号) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, verbose_name用户) status models.CharField(max_length20, choicesStatus.choices, defaultStatus.PENDING_PAY, verbose_name状态) total_amount models.DecimalField(max_digits8, decimal_places2, verbose_name总金额) remark models.CharField(max_length255, blankTrue, default, verbose_name备注) address models.CharField(max_length255, blankTrue, default, verbose_name配送地址) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) paid_at models.DateTimeField(nullTrue, blankTrue, verbose_name支付时间) class Meta: ordering [-created_at] class OrderItem(models.Model): order models.ForeignKey(Order, related_nameitems, on_deletemodels.CASCADE) dish models.ForeignKey(Dish, on_deletemodels.PROTECT) spec models.ForeignKey(DishSpec, nullTrue, blankTrue, on_deletemodels.SET_NULL) dish_name models.CharField(max_length64, verbose_name菜品名称快照) price models.DecimalField(max_digits8, decimal_places2, verbose_name成交单价) quantity models.PositiveIntegerField(default1, verbose_name数量) property def subtotal(self): return self.price * self.quantity这里我把dish_name和price做成了“快照字段”。什么意思呢用户下单之后如果商家修改了菜品名称或价格历史订单里的显示不能跟着变。所以下单时要把当时的名称、价格存一份到明细表里。这个细节一讲出来答辩老师立刻就知道你理解了电商系统的数据不可变原则。3.4 微信小程序的登录态对接小程序端的用户登录和网页端完全不同它没有密码输入。微信生态的标准做法是小程序端调用wx.login获取一个临时code后端拿着code去微信接口换取openid然后自己生成一个业务token返回给小程序。Django端代码逻辑如下# apps/users/views.py import requests from django.conf import settings from django.contrib.auth import get_user_model from rest_framework.decorators import api_view from rest_framework.response import Response User get_user_model() api_view([POST]) def wx_login(request): code request.data.get(code) resp requests.get( https://api.weixin.qq.com/sns/jscode2session, params{ appid: settings.WX_APPID, secret: settings.WX_SECRET, js_code: code, grant_type: authorization_code, }, ) data resp.json() openid data.get(openid) if not openid: return Response({code: 1, msg: 登录失败}, status400) user, created User.objects.get_or_create(usernameopenid, defaults{password: }) token ftoken_{openid} # 毕设可简化生产环境请用JWT return Response({code: 0, data: {token: token, is_new: created}})这个接口有几个点要特别注意code只能使用一次。每次调用wx.login拿到的code有效期大约五分钟且只能用一次。如果前端重复调用后端就会报invalid code。所以前端一定要在每次需要登录时重新调wx.login不能缓存。毕设阶段不用真去注册小程序。微信小程序测试号是可以申请到的直接用测试号的AppID和AppSecret就能调通登录接口。我见过不少同学卡在这一步以为必须企业资质才能开发小程序其实个人开发者完全可以。不建议自己实现JWT。毕设项目直接用后端生成的简单token存缓存或数据库就够了重点是讲清楚“为什么需要token”以及“token与session的区别”这是高频考点。3.5 图片上传与访问配置菜品图片是点餐系统的门面。Django处理上传图片很简单在settings.py里配好MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media然后主路由加一行from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)这样上传的图片就能通过/media/xxx.jpg访问了。小程序端展示图片时直接用全路径URL拼接比如https://你的域名/media/xxx.jpg。注意Django开发服务器在DEBUGTrue时是能正常服务media文件的但部署到服务器或云托管平台后静态文件和媒体文件一般交给Nginx或对象存储处理。如果答辩是在本地运行保持DEBUGTrue即可别为了“生产环境标准”改崩了。4. 小程序前端页面结构、请求封装与购物车状态管理4.1 小程序目录规划与tabBar设计小程序端我推荐的tabBar是四个页签首页、点餐、订单、我的。首页放轮播图和公告点餐页是菜品分类和列表订单页看历史订单我的页面放个人信息和设置。目录结构miniprogram/ ├── app.js ├── app.json ├── app.wxss ├── utils/ │ └── request.js # 请求封装 ├── components/ │ ├── dish-card/ # 菜品卡片组件 │ └── stepper/ # 数量加减组件 └── pages/ ├── index/ ├── menu/ ├── cart/ ├── order-confirm/ ├── order-list/ ├── order-detail/ └── profile/把cart和order-confirm分开是出于交互流程考虑购物车页只负责增删改查菜品确认订单页负责填写备注、选择地址、提交订单。两者合并会让页面逻辑混乱而且用户修改数量后回退时体验也差。页面之间传参建议用>const BASE_URL http://127.0.0.1:8000/api; const request (url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) ? Bearer ${wx.getStorageSync(token)} : , }, success(res) { if (res.statusCode 200 res.statusCode 300) { resolve(res.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); }, }); }); }; module.exports { request, BASE_URL };页面里调用就变得非常轻量const { request } require(../../utils/request); Page({ onLoad() { this.fetchDishes(); }, async fetchDishes() { const res await request(/dishes/); if (res.code 0) { this.setData({ dishes: res.data }); } }, });这个封装的收益在后续联调时极其明显。比如后端要求统一在header里加token、或者所有响应结构从{code, msg, data}改为{status, data}你只需要改这一个文件其他页面自动适配。4.3 首页与点餐页的实现要点点餐页是这个项目里交互最复杂的页面典型的布局是“左分类右列表”。左侧纵向排布菜品分类右侧对应滚动显示菜品列表。实现上核心是两个点左侧分类切换时右侧自动滚动。点击左侧分类右侧scroll-view用scroll-into-view定位到对应分类的锚点位置。每个右侧分类大项设置idcate-1、idcate-2这样的锚点切换时直接改变scroll-into-view的值。右侧滚动时左侧联动高亮。这个稍微复杂一些需要在右侧滚动事件里计算当前滚到哪个分类区域。实现思路是给每个分类块加>cartItem { dishId: 3, dishName: 宫保鸡丁, specId: 12, specName: 大份, price: 28.0, quantity: 2, checked: true, image: http://xxx/a.jpg }如果完全用后端存储每次加购都要请求接口响应速度慢而且如果用户加了菜没登录、或者网络抖动购物车就丢了。如果完全用本地缓存换设备就看不到购物车且后端无法获取用户的加购数据做运营分析。我的方案是折中加购操作先更新本地缓存立即刷新UI体验好同时把加购的数据异步同步到后端Cart表进入购物车页时以后端数据为准本地缓存做兜底这个“本地优先、后端兜底”的策略答辩时也能讲出东西来“为了兼顾响应速度和数据一致性我采用了本地缓存与后端同步的混合方案”。实际开发中很多大厂也是这么干的。4.5 下单流程与订单列表下单流程是购物车页点“去结算” - 确认订单页 - 提交订单 - 模拟支付 - 跳转订单详情。确认订单页的关键逻辑是计算总价。总价 每个购物车项的单价 * 数量之和其中单价 菜品基础价 规格加价。这个价格计算必须由后端在下单接口里重新计算不能相信前端传过来的总价否则用户改个参数就能免费下单。这是一个非常容易在答辩时被问到的安全点。下单接口的核心逻辑# apps/orders/views.py api_view([POST]) def create_order(request): items_data request.data.get(items) total_amount 0 order Order.objects.create( userrequest.user, order_nogenerate_order_no(), statusOrder.Status.PENDING_PAY, ) for item in items_data: dish Dish.objects.get(iditem[dish_id]) spec DishSpec.objects.filter(iditem[spec_id]).first() if item.get(spec_id) else None price dish.price (spec.price_delta if spec else 0) OrderItem.objects.create( orderorder, dishdish, specspec, dish_namedish.name, priceprice, quantityitem[quantity], ) total_amount price * item[quantity] order.total_amount total_amount order.save() return Response({code: 0, data: {order_id: order.id, order_no: order.order_no}})订单号生成一定要唯一我用的是时间戳加随机数的组合datetime.now().strftime(%Y%m%d%H%M%S) str(random.randint(1000, 9999))再在数据库层面加唯一约束。基础好的可以用uuid或雪花算法但毕设用自定义规则更能体现你对业务的理解。5. 前后端联调实测我踩过的五个坑5.1 微信小程序必须HTTPS本地怎么调试第一次做小程序联调的同学大概率会遇到这个问题模拟器里请求http://127.0.0.1:8000能通但真机一打开就报“不在以下合法域名列表中”。原因是微信小程序要求所有请求域名必须是HTTPS且已在后台配置白名单本地IP和localhost在真机上是无法直接访问的。本地调试的土办法是在微信开发者工具里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”这样模拟器可以访问本地接口。真机调试时可以在开发者工具里开启“真机调试”功能它会在手机上建立一个代理通道让真机能访问到本地电脑的服务不需要配HTTPS。如果你需要局域网真机联调注意后端启动要监听0.0.0.0python manage.py runserver 0.0.0.0:8000然后手机和电脑连同一个WiFi请求地址写成电脑的局域网IP比如http://192.168.1.100:8000。前提是手机端的开发者工具同样开了“不校验合法域名”选项正式版小程序里这套是不行的但开发调试阶段完全够用。5.2 CSRF与跨域问题Django默认开启了CSRF防护这是防跨站请求伪造的安全机制。小程序端每次POST请求都可能被Django的CsrfViewMiddleware拦下来报403。解决方案有几个层次第一如果是API接口DRF默认对以/api/开头的接口不做CSRF校验但Django的中间件仍然会拦截。最稳妥的方法是写一个简单的csrf_exempt装饰器from django.views.decorators.csrf import csrf_exempt csrf_exempt api_view([POST]) def create_order(request): ...或者全局配置起来更省心在项目urls.py里统一处理from django.views.decorators.csrf import csrf_exempt from django.conf.urls import url from rest_framework.urlpatterns import format_suffix_patterns # 如果你用的是DRFDRF内部的session认证是自带CSRF豁免的 # 但如果你用了Django原生视图必须手动豁免第二个坑是跨域。小程序请求其实不算浏览器跨域因为小程序不是浏览器环境它不受浏览器同源策略限制。但如果你在开发者工具里用“模拟浏览器环境”的某些调试功能或者后端接了一个Web管理端页面比如另一个Vue项目跨域问题就会出现。解决方案是安装django-cors-headerspip install django-cors-headers在settings.py里配置INSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ALLOW_ALL_ORIGINS True # 毕设直接全放行5.3 时间字段的时区问题这个坑特别隐蔽。如果你在Django的settings.py里设置了TIME_ZONE UTC而你的本机时区是东八区那么数据库里存的created_at比你的本地时间少了8小时。小程序端展示“创建时间”时就会显示一个很诡异的凌晨时间。我的处理方式是TIME_ZONE Asia/Shanghai USE_TZ FalseUSE_TZFalse表示Django在存取时间时不自动做UTC转换直接用本地时间存储。对于单机部署的毕设项目这个配置最简单且最不容易出错。如果你用了USE_TZTrue前端展示时要用toLocaleString()或者做时区偏移转换麻烦还容易错。顺便强调一个时间格式的细节Django ORM返回给前端的DateTimeField默认是2024-05-20T12:00:00.000Z这种ISO格式小程序端用new Date()就能解析。但如果你用了某些序列化器可能返回的是时间戳整数前端也要对应处理。建议在项目开始时就把时间格式统一成ISO字符串。5.4 图片真机显示不出来本地模拟器里图片正常一到真机就空白这个问题在开发小程序时非常常见。原因通常是图片地址是http://localhost:8000/media/xxx.jpg模拟器能识别localhost但真机上的localhost指向手机自己图片地址是http://你的局域网IP:8000/media/xxx.jpg但小程序配置里不允许HTTP加载调试阶段图片地址建议直接用开发者工具的“真机调试”代理或者在后端把图片地址改为可访问的公网地址。如果你有云服务器把后端部署上去图片用https://你的域名/media/xxx.jpg问题自然解决。毕设答辩如果是现场演示最保险的做法是图片路径用相对路径存数据库展示时用一个全局BASE_URL常量拼接。这样你只需要改一个常量所有页面图片都能正常显示。5.5 请求并发与重复下单这是高阶坑但也是可以让你的项目从“普通毕设”升级为“优秀毕设”的点。假设用户快速点击了两次“提交订单”按钮前端会发出两个POST请求后端的create_order会被调用两次生成两个一模一样的订单。用户体验上没有感知到扣了两次款但数据库里出现了重复数据。解决方案至少有三层前端按钮防重复提交时加一个submitting标志请求未返回前禁止再次点击。这是最简单也最有效的一层。后端幂等下单接口接受一个前端生成的client_order_id数据库对它做unique约束第二次相同ID的请求直接返回已存在的订单。一次请求中检查状态在create_order中通过select_for_update()锁定相关数据行。答辩时你只要说出“我在下单接口设计了幂等校验前端也有按钮防重复提交避免重复下单”老师就会觉得你有生产环境意识这是纯功能实现之外的加分项。6. 从“能跑”到“能答辩”数据可视化、演示话术与加分项6.1 加一个商家端Web管理后台这里我强烈建议你花半天时间把Django自带的Admin配置好。不需要额外写前端页面只要在admin.py里注册模型Django自动生成增删改查界面# apps/dishes/admin.py from django.contrib import admin from .models import Category, Dish admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display (name, sort_order) admin.register(Dish) class DishAdmin(admin.ModelAdmin): list_display (name, category, price, status) list_filter (category, status) search_fields (name,)配置好后商家可以登录/admin/后台添加菜品、修改价格、上下架、查看订单。这个后台虽然朴实无华但它是你系统闭环里商家角色最直接的体现。有些同学觉得Django Admin太丑想自己写一套Vue后台。如果是时间充裕且有自信当然可以做。但如果时间紧我建议用Django Admin因为它的数据录入、搜索、筛选、权限控制全都不用你写代码稳得一批。6.2 销售统计与数据可视化“数据可视化”是毕设答辩的天然亮点。你不必做出什么炫酷大屏只需要在后台加一个统计页面展示几张关键图表每日销售额折线图菜品销量排行Top10柱状图分类销售额占比饼图实现方式有两种方式一后端返回统计数据前端用ECharts画。Django聚合查询写出接口from django.db.models import Sum, Count from apps.orders.models import OrderItem def dish_rank(request): stats ( OrderItem.objects .values(dish_name) .annotate(total_qtySum(quantity), total_amountSum(price)) .order_by(-total_qty)[:10] ) return JsonResponse({data: list(stats)})小程序端用echarts-for-weixin库展示这个库是ECharts官方对小程序的适配集成不难。方式二用Django Admin的list_display加统计字段简单但不炫。如果你只想固定图表也可以直接做一个静态HTML用Chart.js从接口拿数据。我个人的建议是网页端用Chart.js或ECharts画一张销售趋势图和一张菜品排行图就足够了。在线点餐系统本质是面向To C的应用数据可视化只是辅助不用追求太多图表。6.3 演示数据的准备技巧答辩演示最怕什么现场点餐页面空白、下不了单。原因往往不是代码有问题而是数据库里没有数据或者下单流程需要完整走一遍但时间不够。我建议你提前准备一批合理的演示数据至少6个菜品分类每个分类3~5个菜所有菜品都配上清晰、无水印的图片预置几个不同状态的订单一笔已完成、一笔进行中、一笔待支付预置一个用户账号和它的历史订单记录更关键的是提前用脚本或手动操作跑通“完整下单链路”把订单状态推进到“已完成”这样数据库里就有真实的关联数据。答辩时打开后台统计数据直接就有图可以讲。如果担心现场网络问题演示时直接用本地跑后端小程序开发者工具不要依赖线上服务器。模拟器里所有请求走本地网络即使没网也能演示。6.4 答辩现场最容易翻车的三个点结合我见过的答辩案例整理三个高频翻车点你提前准备就能避开。项目启动步骤说不清。老师会问“你这个项目我拿过来怎么跑起来”你需要提前准备好一个README.md写清楚安装依赖、迁移数据库、创建管理员账号、启动开发服务器、小程序导入等步骤。最好自己从零按README操作一遍确认没有问题。被问到数据库关系时卡壳。常见追问是菜品和订单明细是什么关系用户和订单是什么关系为什么订单取消后还能查到菜品信息提前把模型关系背熟特别是PROTECT、SET_NULL这些外键策略选择的原因随口能答上来。演示时突然报错手足无措。提前把常见错误准备好比如数据库连接不上、端口被占用、小程序请求域名不合法。甚至可以把这些错误截图打印出来放答辩材料里作为“问题解决记录”这本身就是加分项。还有一个很容易被忽略但也很重要的小细节答辩前把项目里的SECRET_KEY、数据库密码、测试号AppSecret这些敏感信息清掉或替换成占位符不要出现在上传的代码里。一位老师当场打开你的代码看到明文密钥印象分会打折扣。我做了这么多项目最深的体会是毕设不是堆功能而是把一条核心链路走通、走稳再把每个决策的理由讲清楚。在线点餐小程序这个题目难在需要同时管好前端交互、后端逻辑和数据库设计但好处是这三个维度任何一个单独拿出来都有足够的深度可以聊。你把这个系统从“会点菜”做到“能统计、能管理、能适配不同角色”它的完成度和答辩表现都不会差。最后分享一个我自己的习惯所有关键接口的返回格式从第一天就统一成{code: 0, msg: success, data: ...}。很多同学做到一半改格式前端所有页面全崩。定好一个约定就坚持到底后端每次返回都遵守前端解析逻辑也简单。这个习惯能让你在联调阶段少熬至少三个晚上。本文还有配套的精品资源点击获取