1. 高校校园外卖点餐系统的技术选型思考
在高校这个特殊场景下开发外卖点餐系统,我们需要考虑几个核心因素:高并发用餐时段的系统稳定性、移动端适配的灵活性、商家管理功能的完备性,以及学生群体特有的使用习惯。基于这些需求,我选择了Django+Flask+Vue的技术栈组合,这个架构在实际运行中表现出了良好的适应性和扩展性。
Django作为主框架负责核心业务逻辑,其自带的Admin后台完美适配商家管理需求。实测中,Django ORM处理日均3000+订单时,在Properly indexed的MySQL数据库上查询延迟能控制在200ms以内。特别值得一提的是Django的Session中间件,通过配置Redis缓存后,即使在中午12点的订餐高峰时段,登录态保持的稳定性仍能达到99.8%。
Flask则以微服务形式承担支付和通知模块。这个设计源于一次惨痛教训:早期版本将支付直接集成在Django中,结果某次促销活动导致支付阻塞影响了核心订单流程。拆分为独立服务后,我们可以在支付模块单独扩容,使用Gunicorn+Gevent的部署方式轻松应对瞬时支付峰值。
前端选用Vue 2.x版本(考虑到高校场景不需要追求最新技术),其组件化特性特别适合外卖系统的页面结构。比如商家菜单展示就封装为<menu-card>组件,通过props接收不同商家的数据。实测发现,相比传统jQuery方案,Vue的虚拟DOM在移动端列表渲染时能减少40%的页面重绘开销。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计与核心模块实现
2.1 前后端分离的部署方案
系统采用严格的API驱动开发模式,前端Vue应用通过Nginx独立部署,与后端服务解耦。这里有个关键配置:在Nginx中设置跨域白名单时,我们不仅需要配置Access-Control-Allow-Origin,还必须处理预检请求:
nginx复制location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
后端服务部署在Docker Swarm集群上,包含三个核心服务:
- Django主服务(处理订单/用户/商家核心逻辑)
- Flask支付服务(对接微信/支付宝SDK)
- Celery异步任务队列(处理订单状态更新和通知)
2.2 数据库模型设计要点
Django模型设计中,有几个关键优化点值得分享:
python复制class Order(models.Model):
STATUS_CHOICES = [
('unpaid', '待支付'),
('paid', '已支付'),
('preparing', '制作中'),
('delivering', '配送中'),
('completed', '已完成'),
('cancelled', '已取消')
]
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')
shop = models.ForeignKey(Shop, on_delete=models.PROTECT)
items = models.ManyToManyField(MenuItem, through='OrderItem')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='unpaid')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=['status', 'created_at']),
models.Index(fields=['user', '-created_at'])
]
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
item = models.ForeignKey(MenuItem, on_delete=models.PROTECT)
quantity = models.PositiveIntegerField(default=1)
price = models.DecimalField(max_digits=8, decimal_places=2)
特别提醒:on_delete=models.PROTECT在商家和菜单项关联处非常重要,避免误删商家导致历史订单数据异常。我们在生产环境就遇到过商家注销账号后,学生无法查看历史订单详情的问题。
3. 高并发场景下的性能优化实战
3.1 缓存策略的多层设计
高校外卖系统的流量特征非常明显:上午10:30-12:30是订单高峰,占全天流量的65%。我们采用了三级缓存方案:
- CDN静态资源缓存:Vue打包后的js/css文件通过阿里云CDN分发,配置了1年的长期缓存(通过hash文件名解决更新问题)
- Redis热点数据缓存:
- 商家菜单数据:TTL 5分钟 + 主动更新
- 用户购物车:TTL 1小时 + 持久化备份
- Django视图缓存:
python复制@method_decorator(cache_page(60*15), name='dispatch')
class ShopMenuView(DetailView):
model = Shop
template_name = 'shop/menu.html'
3.2 数据库查询优化案例
在订单列表页N+1查询问题的解决上,我们对比了三种方案:
- 原始方案(性能最差):
python复制orders = Order.objects.filter(user=request.user)
# 模板中循环访问 order.items.all()
- 使用select_related(对ForeignKey有效):
python复制orders = Order.objects.select_related('shop').filter(user=request.user)
- 最终方案(prefetch_related + Prefetch对象):
python复制orders = Order.objects.prefetch_related(
Prefetch('items', queryset=MenuItem.objects.only('name', 'price')),
'shop'
).filter(user=request.user).order_by('-created_at')
实测结果:用户订单页加载时间从3200ms降至480ms。这里有个细节:only()方法可以显著减少数据传输量,特别是在菜单项包含大字段(如菜品图片URL)时效果更明显。
4. 商家管理模块的特殊处理
4.1 多角色权限控制系统
系统中有三类商家角色:
- 超级管理员(可管理所有商家)
- 店铺管理员(管理单个店铺)
- 店员(仅处理订单)
我们扩展了Django内置的权限系统:
python复制class ShopAdminGroup(models.Model):
shop = models.OneToOneField(Shop, on_delete=models.CASCADE)
admins = models.ManyToManyField(User, through='ShopAdmin')
class ShopAdmin(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(ShopAdminGroup, on_delete=models.CASCADE)
is_owner = models.BooleanField(default=False)
permissions = models.JSONField(default=list)
在视图层通过装饰器进行权限校验:
python复制def shop_admin_required(perm):
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if not request.user.has_shop_perm(kwargs['shop_id'], perm):
raise PermissionDenied
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
4.2 实时订单通知方案
商家端需要实时接收新订单通知,我们对比了三种技术方案:
- WebSocket:实现简单但高校网络环境复杂,移动端容易断连
- 长轮询:兼容性好但服务器压力大
- SSE(Server-Sent Events):最终选择方案
Flask端的SSE实现:
python复制@app.route('/stream/<int:shop_id>')
def event_stream(shop_id):
def event_stream():
pubsub = redis_client.pubsub()
pubsub.subscribe(f'order_{shop_id}')
for message in pubsub.listen():
if message['type'] == 'message':
yield f"data: {message['data']}\n\n"
return Response(event_stream(), mimetype="text/event-stream")
前端Vue中的处理:
javascript复制const eventSource = new EventSource(`/api/stream/${shopId}`)
eventSource.onmessage = (event) => {
const order = JSON.parse(event.data)
this.$notify({
title: '新订单',
message: `订单号: ${order.number}`,
duration: 0
})
}
5. 踩坑实录与经验总结
5.1 微信支付回调的巨坑
在对接微信支付时,我们遇到了回调验证失败的问题。根本原因是微信的服务器IP会动态变化,而我们的Nginx配置了IP白名单。解决方案是:
- 获取微信支付所有可能的服务器IP段
- 动态更新Nginx配置的脚本:
bash复制#!/bin/bash
WECHAT_IPS=$(dig wechatpay.wechat.com +short | tr '\n' ' ')
sed -i "s/set_real_ip_from.*/set_real_ip_from $WECHAT_IPS;/g" /etc/nginx/nginx.conf
nginx -s reload
5.2 Vue组件性能优化技巧
商家菜单编辑页面对应一个复杂的Vue组件,初始版本在200+菜品时会出现明显卡顿。通过以下优化手段将渲染时间从1200ms降至200ms:
- 虚拟滚动:
vue复制<virtual-list :size="80" :remain="10">
<menu-item v-for="item in filteredItems" :key="item.id"/>
</virtual-list>
- 冻结非响应式数据:
javascript复制export default {
data() {
return {
menuItems: Object.freeze(rawData.map(item => ({...item})))
}
}
}
- 精确控制计算属性:
javascript复制computed: {
filteredItems() {
const start = this.scrollOffset
return this.menuItems.slice(start, start + this.visibleCount)
}
}
5.3 Django Admin的定制化实战
默认的Django Admin无法满足商家后台需求,我们进行了深度定制:
- 列表页添加自定义操作按钮:
python复制@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ['number', 'status', 'custom_actions']
def custom_actions(self, obj):
return format_html(
'<a class="button" href="{}">打印</a>',
reverse('admin:print_order', args=[obj.id])
)
- 添加自定义视图:
python复制def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('print/<int:pk>/', self.admin_site.admin_view(self.print_view))
]
return custom_urls + urls
- 集成富文本编辑器:
python复制class ShopAdminForm(forms.ModelForm):
description = forms.CharField(widget=CKEditorWidget())
class ShopAdmin(admin.ModelAdmin):
form = ShopAdminForm
