一个人在学校里从零开始做毕设,最怕的不是代码难写,而是从一开始就不知道“这到底是个什么东西、该做成什么样”。尤其是“基于微信小程序的校园食堂订餐服务系统”这种题目,看起来烂大街,实际上里面大有文章。今天就把我怎么从需求分析一路做到最终交付、中间踩过哪些坑、哪些设计是后来才发现走了弯路的,完整捋一遍,给你一条可行的路线。
这个系统解决的实际问题其实很具体:一到饭点,食堂窗口排队能绕三个弯,下了课冲过去发现想吃的菜已经卖完。做一个订餐小程序,让用户提前在手机上选好档口和菜品、在线支付或预定,到点之后凭取餐码秒取餐。既能帮食堂按预估量备餐、减少浪费,也省了学生排队时间。对导师来说,这个题目同时覆盖了小程序前端、Python后端、数据库设计、接口联调这几个完整闭环,工作量可控、演示效果直观,很适合作为毕设或课程设计的选题。
如果你是第一次接触这类全栈项目,别慌。我按下面这条线把关键的东西全部拆开:整体怎么设计、后端怎么搭、数据库怎么建、小程序端怎么对接、有哪些坑必须避开。
1. 整体设计思路与架构选择
1.1 核心业务流程梳理
动工之前先别急着敲代码,先把业务流程在白纸上面画出来。我这个系统的核心参与者有三方:学生用户、食堂档口(商家)、系统管理员。
学生端流程是:
- 微信授权登录,拿到openid
- 浏览食堂各档口、菜品分类
- 选菜加入购物车,生成订单
- 模拟在线支付或跳转微信支付
- 查看订单状态,凭取餐码到窗口取餐
- 对已完成的订单进行评价
食堂档口端的操作要更聚焦一些:
- 接收新订单提醒
- 查看订单明细
- 更新出餐状态(接单、制作中、已完成)
- 管理自己的菜品上下架
管理员负责的是:
- 管理食堂和档口信息
- 审核菜品数据
- 查看整体运营数据(订单量、营收、热门菜品)
流程确认之后,把每一个状态机的流转都列清楚:订单从“待支付”到“已支付”,再到“备餐中”“待取餐”“已完成”,还有“已取消”和“已退款”这些异常分支,全都先定好,再写代码,后面就不会被各种状态组合搞到头大。
1.2 为什么选择Python + Django + 微信小程序组合
Python作为后端语言在国内校园项目里非常主流,主要原因是生态成熟、上手速度快、资料多。就算你之前只写过Python基础语法,用Django框架也能在两周内把一套带用户认证、后台管理、ORM模型的后端搭起来。相比Java的Spring Boot,Django对单人或小团队开发友好太多——不用关心繁琐的配置,框架能帮你把八成重复劳动解决掉。
至于为什么不选Flask而选Django,我个人觉得毕设阶段不要给自己找麻烦。Flask确实灵活,但用户登录、数据库迁移、Admin管理后台这些能力都需要自己去拼凑第三方库。Django则是“全家桶”,自带Admin、ORM、Auth、Session、CSRF防护,开发效率和代码规范性都有保障。接口这块用Django REST Framework(DRF)来做,开发API和可视化接口文档会轻松很多。
小程序端选微信原生语法,而不是uni-app或Taro。原因其实很现实:毕设答辩时老师大概率会问“微信小程序的原生生命周期你怎么理解的”,如果直接用跨端框架,对底层机制的了解会被质疑。另外原生框架的工具链更简单,遇到问题社区答案比例最高,文档也最全。跨端方案适合生产项目多端复用,单做一个校园场景的毕设没有必要。
架构上采用前后端完全分离的模式。后端只暴露JSON格式的RESTful API,部署在本机或云服务器;小程序端通过wx.request调用这些API。这样做的好处是职责清晰,前端只管展示和交互,后端专注业务逻辑和数据校验,出了bug排查范围能缩小一半。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 后端环境搭建与核心配置详解
2.1 开发环境准备
这一步会卡住很多人,先把环境列表摆出来:
- Python版本建议3.10以上,理论上3.7也行,但新版Django和DRF对旧版本Python支持不好
- Django选择4.2 LTS版本(不要用5.0这种太新的,部分第三方库还没跟上)
- Django REST Framework 3.15+
- 数据库用MySQL 8.0或者SQLite,本地开发推荐SQLite零配置,但部署演示时建议上MySQL,导师更认可
装好Python之后,建议直接新建虚拟环境,别再把依赖全部装到全局。这一步初期省事,后期能帮你避免大量环境冲突。
bash复制python -m venv venv
# Windows环境
venv\Scripts\activate
# macOS / Linux环境
source venv/bin/activate
pip install django==4.2.* djangorestframework django-cors-headers Pillow
其中django-cors-headers解决跨域问题,Pillow用作头像和菜品图的ImageField处理。微信小程序真机调试时,请求的域名必须是HTTPS,但本地开发时可以在开发者工具里勾选“不校验合法域名”,先用HTTP方式调试。
2.2 Django项目初始化的标准姿势
很多人初始化Django项目喜欢用一句“django-admin startproject”,然后把系统生成的默认文件弄成一座屎山。这里我推荐偏向模块化的组织方式:
bash复制django-admin startproject canteen_system
cd canteen_system
python manage.py startapp user
python manage.py startapp shop
python manage.py startapp dish
python manage.py startapp order
python manage.py startapp comment
为什么要拆成多个App?一开始我也习惯建一个app放所有模型,等代码超过2000行的时候,模型之间互相导入、信号逻辑纠缠不清,改一个字段牵一发动全身。拆开之后每个App只负责自己那块领域,user管用户和身份,dish管菜品,order管订单流转,直观很多。
要特别注意settings.py里要做几个必要配置:
python复制# settings.py核心配置
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'user',
'shop',
'dish',
'order',
'comment',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
# ... 其他中间件保持默认
]
# 允许所有来源,开发阶段方便调试
CORS_ORIGIN_ALLOW_ALL = True
# 后续真机部署时必须收窄,只允许微信官方域名和你的服务器域名
还有一个关键配置是自定义用户模型。因为小程序登录走的是微信授权而不是账号密码,所以自定义用户模型上要加一个openid字段,并且在此基础上建唯一索引,防止同一个微信号注册出多个账号。
python复制from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
openid = models.CharField(max_length=64, unique=True, blank=True, null=True)
nickname = models.CharField(max_length=64, blank=True, default='')
avatar = models.URLField(blank=True, default='')
phone = models.CharField(max_length=20, blank=True, default='')
student_id = models.CharField(max_length=20, blank=True, default='')
balance = models.DecimalField(max_digits=8, decimal_places=2, default=0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'user_user'
2.3 微信小程序登录机制及后端实现方案
小程序登录是整个系统链路里最关键的环节。不了解这个流程,后面所有接口都会因为拿不到用户身份而全部返回401。
微信小程序的登录机制是这样的:小程序端调用wx.login,拿到一个临时凭证code。这个code只能用一次,而且有效期只有5分钟。小程序把这个code传给后端,后端拿着code加上小程序的AppID和AppSecret,去微信的接口换回openid和session_key。openid就是用户在当前小程序下的唯一标识,session_key用来解密用户手机号等敏感数据。
后端的实现逻辑是:
python复制import requests
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
class WxLoginView(APIView):
def post(self, request):
code = request.data.get('code')
appid = settings.WX_APPID
secret = settings.WX_SECRET
url = (
f'https://api.weixin.qq.com/sns/jscode2session'
f'?appid={appid}&secret={secret}&js_code={code}'
f'&grant_type=authorization_code'
)
resp = requests.get(url).json()
openid = resp.get('openid')
if not openid:
return Response({'error': 'code无效'}, status=400)
# 查库或创建用户
user, _ = User.objects.get_or_create(openid=openid)
# 生成自定义token
token = jwt_encode(user)
return Response({'token': token, 'user_id': user.id})
后端拿到openid之后会签发自己的登录态token。这里我用的是PyJWT生成的token,设置7天过期时间。小白千万别想着自己发明加密方案,直接用标准库。小程序端保存这个token,之后每次请求都放在Header里,后端用认证类解析。
还有个开发者很容易忽略的生产细节:小程序端wx.login每次调用返回的code都不一样,有些同学在一个页面里调了十几次wx.login,然后每次都用最新的登录态去请求,白白浪费性能。合理做法是App启动时只调用一次wx.login,把拿到的token存到storage里反复使用。
3. 数据库模型设计与方案选型
3.1 菜品、购物车与订单模块的表结构设计
数据库是这类系统最容易埋雷的地方。如果表结构设计得太随意,比如把购物车数据直接塞到localStorage里、把订单里的菜品详情当成长字符串存,那后期统计和状态管理一定会出问题。
我的表结构设计经验可以总结成下面这些核心模型:
用户表(User):除了Django自带的字段,增加了openid、昵称、头像、学号、余额,覆盖这个小程序场景的所有用户属性。不要在这张表里存冗余的统计字段,比如“购买次数”这种,需要用的时候count查询加上索引就够了。
菜品分类表(Category):字段最多就一个分类名和排序值,非常轻量。省下来的胃口留给外键关联。
菜品表(Dish):绑定具体的档口(shop_id)和分类,包含名称、图片、描述、月销量、价格、库存、状态等。注意,状态字段要比布尔值好,比如用1表示上架、0表示下架,字段的可读性和扩展性都更好。
档口表(Shop):食堂下面会分档口,档口是后勤组织的核心概念,背后是商家。这个表承担“某食堂-某档口-若干菜品”的层级关系。
订单表(Order):核心中的核心,字段很多也很关键。总金额、订单编号、状态、支付时间、取餐号、用户ID、备注、创建时间。这里我给订单编号存成一个独立字段而不是依赖自增ID,原因是展示给用户的订单号和内部主键解耦可以防止用户通过订单号猜出业务量。
订单明细表(OrderItem):每个订单可以包含多个菜品,菜品快照存这里。之所以叫“快照”,是因为这个表里必须冗余一份菜名、单价、图片,不能等订单成立后再去join菜品表。否则商家后来改了菜品名和价格,历史订单的数据就被污染了。
购物车表(Cart):本意是让用户先选菜再统一提交,但可以和订单表合并考虑。我最后是给临时未下单的购物车单独建了个轻量表(cart_id + user_id + dish_id + quantity + checked),下单成功后清空对应选中项。
评价表(Comment):用户对某笔订单的总体评分和文字评价,加上评价时的图片,关联订单号可以防止用户不消费就刷评价。
3.2 状态机设计保证订单数据不混乱
订单状态是这个系统里最容易出bug的地方,没有一个清晰的状态机,“已支付订单能不能取消”“档口已接单后用户能不能退款”这类边界情况就会在代码里散落成无数个if判断,最后根本维护不了。
我的做法是将订单状态定义成固定的数字枚举:
| 状态码 | 含义 | 用户可操作动作 |
|---|---|---|
| 0 | 待支付 | 取消订单 |
| 1 | 已支付/待接单 | 无,等待商家接单 |
| 2 | 商家已接单/制作中 | 申请退款(需商家同意) |
| 3 | 待取餐 | 凭取餐码取餐 |
| 4 | 已完成 | 发起评价 |
| 5 | 已取消 | 无 |
| 6 | 已退款 | 无 |
后端每次变更状态必须走唯一的服务函数,禁止在视图里随手直接修改state字段。比如支付成功回调里负责把0变1,商家端接单接口负责把1变2,取餐接口负责把3变4,只有中心化管理状态流转,才能避免你在这个接口改成4、那个接口又改成5,数据全乱的情况。
从0流转到5(取消)其实分两种情况:用户主动取消,和支付超时自动取消。支付超时取消建了一个定时任务去扫,超过15分钟仍未支付的单子自动关闭并释放库存。这套逻辑在毕设里可以用Django的定时任务实现,也可以用celery做分布式任务调度。只为了跑毕设,用简单的定时脚本就够了,别把技术栈搞得过于复杂到自嗨。
4. 小程序端核心功能模块实现过程
4.1 首页食堂列表、菜品浏览与搜索
原生的微信小程序页面结构是由wxml、wxss、js、json四个文件组成的。首页我规划成三个信息层级:顶部搜索栏、食堂分类tab、下方菜品推荐流。
食堂轮播和分类导航在首页首屏要短平快看出重点,数据从后端DishCategory和Dish的接口拉取。菜品图片必须用CDN或后端服务器可访问的绝对地址,不要用相对路径。
搜索功能我加了一个防抖机制,这里的“防抖”和前端“debounce”还不太一样。小程序input的bindinput每次输入都触发,不设防抖每秒会发十几个请求。我用setTimeout做了个300ms延迟处理,防止输入过程中频繁请求接口:
javascript复制onSearchInput(e) {
const keyword = e.detail.value
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.fetchSearchResult(keyword)
}, 300)
}
菜品列表接口设计成支持关键词搜索、分类筛选和分页。Django后端用DRF的过滤组件和分页组件,不用自己拼SQL,代码非常简洁:
python复制from rest_framework import generics, filters
from django_filters.rest_framework import DjangoFilterBackend
class DishListView(generics.ListAPIView):
serializer_class = DishSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_fields = ['category_id', 'shop_id', 'status']
search_fields = ['name', 'description']
def get_queryset(self):
return Dish.objects.select_related('shop', 'category').filter(status=1)
4.2 购物车和订单确认页面的交互设计
购物车这个模块看着不起眼,但做得好不好用直接影响用户有没有下单冲动。我采用的方案是底部悬浮购物车栏,点击展开以后看到购物车明细,支持单选框勾选要结算的菜品、左右滑动删除单项、点击加减号修改数量。
小程序的setData是同步渲染到视图层的,但如果页面数据太深或者数组太大,频繁操作setData会造成明显的掉帧卡顿。购物车里每一项的变更我都只更新单项而不是全量更新整个列表:
javascript复制updateQuantity(index, delta) {
const key = `cartItems[${index}].quantity`
const newVal = this.data.cartItems[index].quantity + delta
this.setData({ [key]: newVal })
}
订单确认页要展示的信息比较多:收货方式(自取还是堂食,校园场景一般没有配送)、选择取餐档口、优惠信息、备注、支付方式、预计取餐时间。这个页面的数据是从购物车中勾选的菜品快照带过来的。后端下单接口接受的是一个菜品ID与数量数组,后端校验库存后生成订单和订单明细,并将结果连同取餐码一起返回小程序。
提交订单时一定要加防重复提交逻辑。用户在弱网下点了一下没反应,大概率会再点一下,这时候就会产生两笔相同的订单。处理方案是前端在后端返回结果以前把按钮置为loading并禁用,后端再做一个幂等校验:同一个用户同一秒内相同内容订单不允许重复创建。前后两层夹击才能把这个坑彻底堵住。
4.3 支付流程设计:真实支付与模拟支付
真正接入微信支付需要企业主体的小程序账号,并且要开通微信支付商户号。校园项目的尴尬之处在于个人主体申请的微信小程序没法开通微信支付,所以在毕设和课程设计场景下,普遍做法都是模拟支付。
模拟支付流程的设计思路是:用户从订单确认页点击“去支付”,后端生成一个支付单,小程序跳转到模拟收银台页面,用户点“确认支付”,后端直接把订单状态改为“已支付”,并生成一个取餐码。为了让演示效果更逼真,我在模拟收银台页面加了倒计时和支付成功动画,模拟了从“收银台中”到“支付成功”的体验过程。
如果真要用企业主体做演示,可以用微信支付V3的JSAPI下单模式,后端先调用“统一下单”接口拿到prepay_id,然后用参数签名后返回给小程序,小程序端用wx.requestPayment唤起收银台。这个流程和微信登录一样,核心点在于参数签名必须严格按微信官方文档的顺序排序再生成签名,少任何一个字段都会报签名错误。
5. 数据库设计和接口文档的核心逻辑
5.1 核心表的ER关系设计
后端代码量起来之前,最好先把数据库的ER图设计好。我的最终数据库一共有8张表,核心的关联关系是这样的:
- 一个User对应多个Order,一个Order对应多个OrderItem
- 一个Shop对应多个Dish,一个Category也对应多个Dish
- 一个Order必须关联一个User和一个Shop
- 每个OrderItem必须关联一个Order和一个Dish,但菜品信息是冗余快照的
- Comment表关联Order,但Order到Comment是OneToOne
- Cart表是User与Dish的多对多关系,带数量和勾选状态
外键关系要用好,但不是越多越好。以前见过有同学给每张表都加created_by这种外键,结果删一个档口级联删掉几百个菜品。设计的时候要想清楚每个外键是不是“业务上必须存在的从属关系”。比如Dish表关联Shop是必须的,因为每个菜都属于一个档口;但是OrderItem和Dish的关系就可以松一些,用整型字段保存dish_id就够了,因为OrderItem本身已经是下单时刻的快照了。
5.2 RESTful API接口规划
写后端接口之前,先规划好接口文档的路径和参数,不要把接口写得像打地鼠一样,今天多一个明天改一个。我列出自己项目里最核心的接口路径:
| 接口路径 | 方法 | 功能说明 | 请求参数 |
|---|---|---|---|
| /api/user/login | POST | 微信授权登录,换token | code |
| /api/canteens/ | GET | 获取食堂列表 | 无 |
| /api/shops/?canteen_id=1 | GET | 获取指定食堂的档口 | canteen_id |
| /api/dishes/?shop_id=1 | GET | 获取档口下的菜品列表 | shop_id, keyword, page |
| /api/cart/ | GET/POST/DELETE | 查看/添加/清空购物车 | dish_id, quantity |
| /api/orders/ | POST | 创建订单 | items:[{dish_id,qty}], shop_id, remark |
| /api/orders/?status=1 | GET | 查询订单列表 | status |
| /api/orders/{id}/ | GET | 获取订单详情 | 无 |
| /api/orders/{id}/cancel/ | POST | 取消订单 | 无 |
| /api/orders/{id}/pay/ | POST | 模拟支付 | 无 |
| /api/orders/{id}/confirm/ | POST | 标记取餐(凭取餐码) | code字段 |
| /api/orders/{id}/comment/ | POST | 评价订单 | content, rating |
| /api/admin/overview/ | GET | 管理员获取统计面板 | 无 |
以上接口设计有几个特点:一是资源命名全用复数名词,符合RESTful惯例;二是带状态筛选的操作都通过Query String传status;三是将订单操作抽象成子资源动作(cancel、pay、confirm、comment),语义清晰。
写接口文档时用DRF的自动文档功能,或者在项目docs目录下维护一份Markdown版API文档。很多人觉得这是形式主义,但答辩的时候老师问“你的接口怎么给别人用”,你直接甩出一个文档页面,观感会好很多。
6. 常见问题排查与避坑实录
6.1 微信小程序真机调试的常见故障
这类系统开发期在模拟器里跑得好好的,一上真机就崩,这类问题我见得多了。真机与开发者工具最大的差异在于网络环境、权限系统、以及对非法域名的校验策略。这里总结几个高频问题:
第一个:真机请求一直报“URL not in whitelist”。开发者工具里勾了“不校验合法域名”就万事大吉,但真机完全不认这一套。解决办法是在微信公众平台的开发设置里配置request合法域名,域名必须ICP备案且支持HTTPS。这个环节往往被所有人拖到最后一天才处理,建议项目开始第3天就去申请域名和证书,因为ICP备案的时长可能会超出你的心理预期。
第二个:微信开发者工具上页面一切正常,真机预览白屏且控制台没有报错。这种情况八九不离十是使用了ES6+的某个特性但项目没开ES6转ES5,老版本基础库不支持。在开发者工具的“详情-本地设置”里勾选“ES6转ES5”,并确认调试基础库版本别选太新的。
第三个:图片加载不出来。模拟器上图片正常是真机显示失败,多半是你的图片服务器HTTP没配HTTPS,或者图片URL里带了localhost这种本机地址。真机没法访问你电脑的localhost。解决方法是把后端跑在局域网并用局域网IP访问,或者直接把图片传到云存储和对象存储上,返回公网URL,小程序显示会稳定得多。
第四个:涉及位置权限和摄像头权限的API,在模拟器上没弹授权,真机上必须要处理用户拒绝授权的场景。做取餐码扫码功能时一定要在调用wx.scanCode前检查授权状态,被拒绝后要有“重新授权”的引导按钮。
6.2 高并发下订单数据错乱的处理思路
校园食堂高峰期有“秒杀”的特性——午饭11点30分下课,几千人同时冲进来下单。虽然毕设演示时不见得有这么大的并发量,但如果你把系统做得很卡,至少会暴露几个逻辑问题。
最典型的就是“超卖”问题:菜品表里库存只剩2份,但同一秒有3个用户同时下单成功。原因在于后端处理顺序是先查库存再扣库存,查询和扣减之间不是原子操作。使用Django的ORM时,正确的扣库存方式是用F表达式配合条件更新:
python复制from django.db.models import F
from django.db import transaction
@transaction.atomic
def create_order(user, shop_id, items):
# 扣减库存,条件中带上库存充足判断
for item in items:
dish = Dish.objects.filter(
id=item['dish_id'],
stock__gte=item['quantity']
).update(stock=F('stock') - item['quantity'])
if dish == 0:
raise Exception('库存不足')
另一个在订单生成中容易遇到的坑是事务边界不清晰。订单表加订单明细表要同时成功或同时失败,必须放在同一个事务里。Django的transaction.atomic装饰器在这儿就派上用场了。另外注意事务作用域里别穿插外部HTTP请求,比如你以为支付成功了去调通知接口,外部服务如果超时,你的数据库事务会一直悬着,最后连接池爆掉。
6.3 关于校园隐私与个人数据保护的合规建议
做校园系统有一个经常被忽略的问题,就是数据合规。很多同学会收集大量用户手机号、学号、甚至精确到宿舍号的信息,却完全没有安全意识。这里建议处理好以下几点:
第一,只收集业务必需的最小个人信息。食堂订餐系统,用户下单和取餐核验所需要的最少信息就是openid、昵称、头像。手机号和学号不是必须的时不要收集,收集了就会背上额外安全保障义务。
第二,微信授权手机号需要使用企业认证的小程序,个人主体的项目获取不到。不要用“让用户自己填手机号”这种方式绕过,因为数据保护责任不会因为你用了最土的方法就消失。
第三,存储用户敏感数据时必须加密。openid算是准敏感数据,密码字段肯定不能用明文、不能简单哈希,必须用Django的PBKDF2或bcrypt算法。手机号、学号这类字段建议加密存储,至少不能在小程序端明文分发。
第四,隐私政策文档和用户协议单独写清楚,放在登录注册页。虽然答辩老师可能不细看,但一旦系统要真实上线,这就是保护自己的第一道防线。从代码里打日志时,不要把日志里顺手打上用户完整手机号和学号。真有调试需求时打后四位就够定位了。
7. 进阶优化与后续扩展方向
7.1 数据统计报表与管理员看板
基础功能做完之后,我还额外做了一个针对管理员的可视化看板。因为单纯给出“食堂订餐、档口管理、订单管理”这些基础功能,横向对比并没有太大优势。加一个管理员的统计面板,实现菜品销售排行、时段订单量热力图、近7日营收曲线这类图表,系统会立刻显得完整很多。
实现方案是后端提供聚合统计API,用Django ORM的annotate和Count/Sum做聚合查询,返回按天统计的订单量和营收:
python复制from django.db.models import Count, Sum
from django.db.models.functions import TruncDate
def dashboard(request):
today_stats = (
Order.objects
.filter(created_at__date=timezone.now().date())
.aggregate(
total_orders=Count('id'),
total_revenue=Sum('total_amount')
)
)
# 按天汇总近30天的数据
trend = (
Order.objects
.filter(created_at__gte=timezone.now() - timedelta(days=30))
.annotate(day=TruncDate('created_at'))
.values('day')
.annotate(
orders=Count('id'),
revenue=Sum('total_amount')
)
.order_by('day')
)
前端用echarts的微信小程序版组件或用canvas自绘柱状图、折线图。当时第一个版本图表的展示效果做得挺吃力的,后来找到ecology的echarts-for-weixin库,配置一次组件全局使用,就省了九成工作量。
7.2 排队叫号与消息推送的场景扩展
校园食堂订餐落地以后,现场取餐的秩序问题又暴露出来了:用户到窗口后发现餐没做好,或者窗口做完餐找不到用户来取。进一步做“排队叫号大屏”会是很有亮点的扩展方向。
这个方案本质上是给档口配一个叫号屏。我尝试用小程序的web-view嵌一个H5大屏页面,档口商家在平板上能实时看到当前队列。技术上实现起来也不复杂:后端提供一个取餐状态的WebSocket接口,或者简单做法就是前端每5秒轮询一次订单状态接口。把新订单推送、出餐提醒挂在新订单提醒功能里,比语音播报要传统一些,但演示效果很稳。
小程序订阅消息下发也是一个很好的扩展。用户下单支付成功后,后端调用微信subscribeMessage.send接口向用户推送“订单已接单”“餐品已备好,请尽快取餐”的服务通知。注意做这个功能需要在小程序后台申请订阅消息模板,并在前端引导用户授权订阅。引导的时机要放在用户刚点击支付按钮时——用户在这个瞬间有明确的下单意愿,授权通过率会更高。
7.3 结合Django缓存提升接口响应速度
业务量上来之后,像菜品列表、食堂列表这类读多写少的接口,每个用户打开刷一次都直接查一次数据库,完全没有必要。引入Django的缓存框架,把热门接口的结果缓存住,能有效减轻数据库的压力。
用Redis作为缓存后端,配置好之后给ListAPIView的查询集加缓存decorator:
python复制from django.core.cache import cache
from rest_framework.response import Response
def dish_list(request):
cache_key = f"dish_list_{request.query_params.get('shop_id', 'all')}"
data = cache.get(cache_key)
if data is None:
queryset = Dish.objects.filter(status=1).select_related('shop')
data = DishSerializer(queryset, many=True).data
# 缓存300秒
cache.set(cache_key, data, 300)
return Response(data)
请注意一个关键问题:菜品上架、库存修改操作时必须主动删除对应的菜品缓存。哪怕影响了一点性能也比用户下单后看到“已售罄”却还是缓存的“有货”状态要强。这样的联动关系在毕设设计报告里也值得专门写一节,作为业务逻辑自洽的证据。
写在最后的个人体会
这套系统从最初确定方案到最终调试通过,我走了一个半月。回头看看,如果让我重新做一遍,重点布局应该放在“数据库设计”和“权限授权流程”,而不是急着追求炫酷的页面效果。微信小程序的登录流程、后端状态机的流转控制、以及库存扣减的原子性问题,是这类系统最容易出问题的地方,也是答辩时老师最爱追问的三个切入点。
另外一个特别想提醒的是:接口路径和参数名一定要前后端约定好,再做前端。不要写一个接口就对接一个接口,项目中期前后端接口文档不一致的矛盾会消耗掉大量时间。用Apifox这类工具维护一份团队共享的接口文档,或者在Django的DRF自动文档页维护好,能让你省下每天“你传参是id还是pk”“那个字段怎么拼写来着”之类的无效沟通成本。
最后再说一个不那么技术、但很重要的判断:不要把毕设当成追求新技术的试验田。只要涉及个人开发,尽量选择“社区成熟、踩坑多、解决方案全”的标配套路。Python搭Django做后端、微信原生写小程序、MySQL存数据,已经罩得住这个题目下的所有核心要求。想加分就往数据分析、消息推送优化、排队叫号这些“系统完善度”方向上做,而不是抱怨这个框架不如那个框架cool。等你从开题走到答辩验收那一天,你会觉得当时还是这个选择最稳妥。
