校园食堂点餐系统的背景与意义
技术背景
移动互联网的普及推动了校园服务的数字化需求。微信小程序凭借无需安装、即用即走的特性,成为校园场景的理想载体。Django作为Python的高效Web框架,具备快速开发、安全性和可扩展性优势,适合处理订单、支付等复杂业务逻辑。
现实痛点
传统食堂就餐高峰时段存在排队拥挤、人工点餐效率低、错单漏单等问题。学生需现场等待,时间成本高;食堂运营方缺乏数据化手段分析菜品偏好,难以优化供应链和菜单设计。
系统意义
学生侧:
- 线上浏览菜单、提前下单,减少排队时间,提升就餐体验。
- 支持个性化需求(如忌口备注),历史订单可追溯。
食堂管理侧:
- 数字化订单管理降低人工错误率,实时监控菜品销量,动态调整备餐量。
- 数据分析模块帮助优化菜品结构,减少食材浪费。
技术验证价值:
- 结合Django后端与微信小程序前端,为校园场景提供轻量化解决方案范例。
- 可扩展至外卖配送、校园卡支付等模块,形成智慧校园生态。
社会效益
减少人群聚集(如疫情期间),推动绿色校园建设(通过精准备餐降低食物浪费),符合教育信息化发展趋势。
技术栈概述
开发基于Django和微信小程序的校园食堂点餐系统,需结合后端、前端、数据库及微信生态相关技术。以下是关键组件及工具:
后端技术
- Django框架:提供核心后端逻辑,包括用户认证、订单管理、菜单API等。
- Django REST Framework:构建RESTful API,支持微信小程序的数据交互。
- 数据库:MySQL或PostgreSQL存储用户、订单、菜单等结构化数据。
- 缓存:Redis用于高频访问数据(如菜单列表)的缓存,提升响应速度。
- 异步任务:Celery处理耗时操作(如订单状态通知),搭配RabbitMQ或Redis作为消息队列。
微信小程序端
- 微信开发者工具:开发和调试小程序界面。
- WXML/WXSS:构建页面结构和样式,类似HTML/CSS。
- JavaScript/TypeScript:实现小程序交互逻辑,调用后端API。
- 微信支付API:集成支付功能,支持订单结算。
- 微信云开发(可选):快速部署小程序云函数或存储静态资源。
辅助工具与服务
- Nginx:反向代理和静态资源托管。
- Docker:容器化部署,简化环境配置。
- JWT/OAuth2:用户身份验证,保障接口安全。
- WebSocket:实时推送订单状态(如取餐通知)。
扩展功能建议
- 数据分析:通过Pandas或Django插件分析订单数据,优化食堂运营。
- 消息推送:使用微信模板消息或订阅消息通知用户订单动态。
部署与运维
- Linux服务器:推荐Ubuntu/CentOS运行Django服务。
- CI/CD:GitHub Actions或Jenkins实现自动化测试与部署。
此技术栈平衡了开发效率与系统性能,适合校园场景的高并发需求。
以下是基于Django和微信小程序的校园食堂点餐系统核心代码实现,涵盖后端API、数据库模型及微信小程序交互逻辑的关键部分:
后端Django核心代码
数据库模型(models.py)
from django.db import models from django.contrib.auth.models import User class Canteen(models.Model): name = models.CharField(max_length=50) location = models.CharField(max_length=100) opening_hours = models.CharField(max_length=100) image_url = models.URLField() class Food(models.Model): canteen = models.ForeignKey(Canteen, on_delete=models.CASCADE) name = models.CharField(max_length=100) price = models.DecimalField(max_digits=6, decimal_places=2) description = models.TextField() image_url = models.URLField() stock = models.IntegerField(default=0) is_available = models.BooleanField(default=True) class Order(models.Model): ORDER_STATUS = ( ('pending', '待支付'), ('paid', '已支付'), ('cooking', '制作中'), ('ready', '待取餐'), ('completed', '已完成'), ('cancelled', '已取消') ) user = models.ForeignKey(User, on_delete=models.CASCADE) order_number = models.CharField(max_length=20, unique=True) total_amount = models.DecimalField(max_digits=8, decimal_places=2) status = models.CharField(max_length=20, choices=ORDER_STATUS, default='pending') created_time = models.DateTimeField(auto_now_add=True) pickup_time = models.DateTimeField(null=True, blank=True) class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items') food = models.ForeignKey(Food, on_delete=models.CASCADE) quantity = models.IntegerField() price = models.DecimalField(max_digits=6, decimal_places=2)视图逻辑(views.py)
from rest_framework import viewsets, status from rest_framework.response import Response from .models import Canteen, Food, Order from .serializers import * from wechatpy import WeChatPay class FoodListView(viewsets.ModelViewSet): queryset = Food.objects.filter(is_available=True) serializer_class = FoodSerializer def get_queryset(self): canteen_id = self.request.query_params.get('canteen_id') if canteen_id: return self.queryset.filter(canteen_id=canteen_id) return self.queryset class OrderCreateView(viewsets.ModelViewSet): queryset = Order.objects.all() serializer_class = OrderSerializer def create(self, request): serializer = OrderCreateSerializer(data=request.data) if serializer.is_valid(): order = serializer.save(user=request.user) # 微信支付预处理 pay = WeChatPay( appid='YOUR_APPID', api_key='YOUR_API_KEY', mch_id='YOUR_MCH_ID' ) payment = pay.order.create( trade_type='JSAPI', body='校园食堂点餐', total_fee=int(order.total_amount * 100), out_trade_no=order.order_number, openid=request.user.openid ) return Response({ 'order': OrderSerializer(order).data, 'payment': payment }, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)微信小程序核心代码
订单页面逻辑(order.js)
Page({ data: { cartItems: [], totalPrice: 0, canteenInfo: {} }, onLoad: function(options) { this.loadCanteenInfo(options.canteenId); this.loadCart(); }, loadCanteenInfo: function(canteenId) { wx.request({ url: 'https://yourdomain.com/api/canteens/' + canteenId, success: (res) => { this.setData({ canteenInfo: res.data }); } }); }, createOrder: function() { const that = this; wx.login({ success: (res) => { wx.request({ url: 'https://yourdomain.com/api/orders/', method: 'POST', data: { items: this.data.cartItems, total_amount: this.data.totalPrice }, header: { 'Authorization': 'Bearer ' + getApp().globalData.token }, success: (res) => { wx.requestPayment({ timeStamp: res.data.payment.timeStamp, nonceStr: res.data.payment.nonceStr, package: res.data.payment.package, signType: 'MD5', paySign: res.data.payment.paySign, success: () => { wx.redirectTo({ url: '/pages/order/order_detail?id=' + res.data.order.id }); } }); } }); } }); } });菜品列表页面(menu.js)
Page({ data: { foods: [], canteenId: null }, onLoad: function(options) { this.setData({ canteenId: options.canteenId }); this.loadFoods(); }, loadFoods: function() { wx.request({ url: 'https://yourdomain.com/api/foods/', data: { canteen_id: this.data.canteenId }, success: (res) => { this.setData({ foods: res.data }); } }); }, addToCart: function(e) { const food = e.currentTarget.dataset.food; getApp().globalData.cart.addItem(food); wx.showToast({ title: '已添加', icon: 'success' }); } });关键功能实现说明
微信支付集成
使用wechatpy库处理微信支付流程,后端生成预支付订单后返回支付参数给小程序端调用支付接口。
购物车管理
小程序端使用全局变量管理购物车状态,订单提交时将购物车数据发送至后端创建订单。
库存管理
在Food模型中设置stock字段,下单时需检查库存并更新:
def create_order_items(order, items): for item in items: food = Food.objects.get(pk=item['food_id']) if food.stock < item['quantity']: raise ValueError(f"{food.name}库存不足") food.stock -= item['quantity'] food.save() OrderItem.objects.create( order=order, food=food, quantity=item['quantity'], price=food.price )取餐通知
可通过微信模板消息或小程序订阅消息推送取餐通知:
def send_pickup_notification(order): from wechatpy import WeChatClient client = WeChatClient('APPID', 'APPSECRET') client.message.send_template( user_id=order.user.openid, template_id='TEMPLATE_ID', data={ 'first': {'value': '您的餐品已准备好'}, 'keyword1': {'value': order.order_number}, 'keyword2': {'value': str(order.total_amount)}, 'remark': {'value': '请及时取餐'} } )Django微信小程序校园食堂点餐系统设计
数据库设计
用户表(User)
- 字段:
user_id(主键)、openid(微信唯一标识)、nickname、avatar_url、phone、balance(余额)、create_time。 - 索引:
openid需唯一索引以快速匹配微信用户。
- 字段:
食堂表(Canteen)
- 字段:
canteen_id(主键)、name、location、opening_hours、image_url。
- 字段:
窗口表(Window)
- 字段:
window_id(主键)、canteen_id(外键关联食堂)、name、status(营业状态)。
- 字段:
菜品表(Dish)
- 字段:
dish_id(主键)、window_id(外键关联窗口)、name、price、image_url、description、stock(库存)。
- 字段:
订单表(Order)
- 字段:
order_id(主键)、user_id(外键)、total_price、status(待支付/已支付/已完成)、create_time、pay_time。 - 索引:
user_id和create_time联合索引便于查询历史订单。
- 字段:
订单详情表(OrderDetail)
- 字段:
detail_id(主键)、order_id(外键)、dish_id(外键)、quantity、price(下单时菜品价格快照)。
- 字段:
支付记录表(Payment)
- 字段:
payment_id(主键)、order_id(外键)、amount、transaction_id(微信支付单号)、status、pay_time。
- 字段:
关键代码示例
# models.py示例 from django.db import models class Dish(models.Model): window = models.ForeignKey('Window', on_delete=models.CASCADE) name = models.CharField(max_length=100) price = models.DecimalField(max_digits=8, decimal_places=2) image_url = models.URLField() description = models.TextField(blank=True) class Order(models.Model): STATUS_CHOICES = [ ('pending', '待支付'), ('paid', '已支付'), ('completed', '已完成') ] user = models.ForeignKey('User', on_delete=models.CASCADE) total_price = models.DecimalField(max_digits=10, decimal_places=2) status = models.CharField(max_length=20, choices=STATUS_CHOICES) create_time = models.DateTimeField(auto_now_add=True)系统测试方案
功能测试
用户登录测试
- 模拟微信授权流程,验证
openid是否正确获取并存入数据库。 - 边界测试:新用户首次登录时自动注册逻辑。
- 模拟微信授权流程,验证
下单流程测试
- 创建测试订单,验证库存扣减、订单状态流转(待支付→已支付→已完成)。
- 并发测试:模拟多人同时下单同一菜品时的库存冲突处理。
支付回调测试
- 使用微信沙箱环境模拟支付成功/失败回调,验证订单状态和余额变动。
性能测试
高并发场景
- 使用JMeter模拟用餐高峰期的并发请求(如1000+用户同时访问菜单页面)。
- 监控数据库查询响应时间,优化慢查询(如添加缓存层Redis)。
数据库压力测试
- 生成10万条历史订单数据,测试分页查询性能。
- 验证索引有效性:通过
EXPLAIN ANALYZE分析关键查询。
安全测试
接口防护
- 测试未授权访问敏感接口(如直接调用支付完成接口)。
- 验证CSRF Token和微信会话校验机制。
数据安全
- SQL注入测试:尝试通过菜品名称字段注入恶意代码。
- 敏感信息脱敏:用户手机号在日志中的显示处理。
自动化测试
# tests.py示例 from django.test import TestCase from rest_framework.test import APIClient class OrderTest(TestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create(openid='test_openid') def test_create_order(self): response = self.client.post('/api/orders/', data={'user_id': self.user.id}, format='json') self.assertEqual(response.status_code, 201)