1. 项目概述与技术选型
校园外卖平台是一个典型的Web应用系统,需要同时满足学生用户、商家和管理员三方的需求。作为开发者,我们需要在技术选型上兼顾开发效率、系统性能和可维护性。经过多次项目实践验证,Python+Vue的组合确实能很好地平衡这些需求。
Python作为后端语言,最大的优势在于其丰富的Web开发生态。Django和Flask这两个框架各有千秋:
- Django适合需要快速搭建完整后台的项目,它自带的Admin后台、ORM和全套认证系统能节省大量开发时间。我在一个日订单量3000+的校园平台中就使用了Django,它的自带功能覆盖了80%的后台需求。
- Flask则更适合需要灵活定制的场景。去年开发的一个多商户外卖系统就采用了Flask+Blueprint的架构,可以很方便地为不同商家提供定制化接口。
前端选择Vue.js主要基于三点考虑:
- 组件化开发模式与外卖平台的高度模块化特性完美契合(菜品展示、购物车等都可以做成独立组件)
- 响应式数据绑定能实时反映订单状态变化
- 丰富的UI库(如Element UI)可以快速构建美观界面
开发工具方面,PyCharm Professional版对Django/Vue的全栈支持确实出色,特别是它的数据库工具和HTTP Client对接口调试帮助很大。不过学生党用社区版+VSCode组合也完全够用。
2. 系统架构设计
2.1 功能模块划分
一个健壮的校园外卖平台应该包含三大终端:
用户端核心功能:
- 基于JWT的认证体系(特别注意要做好refresh token的轮换机制)
- 基于地理位置的商家筛选(建议使用Redis GEO)
- 购物车的本地存储与服务端同步方案
- 支付流程的防重复提交设计
商家端特殊需求:
- 菜品上下架的批量操作接口
- 订单打印的WebSocket实时推送
- 营业数据的可视化展示(我用ECharts实现的周销量热力图效果很好)
管理后台关键点:
- 基于RBAC的权限控制系统
- 定时任务管理(如自动结算商家账款)
- 敏感操作的审计日志
2.2 数据库设计实战
MySQL表设计有几个需要特别注意的地方:
sql复制-- 订单表需要特别注意状态流转
CREATE TABLE `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint NOT NULL,
`shop_id` bigint NOT NULL,
`status` tinyint NOT NULL COMMENT '0待支付 1已支付 2已接单 3配送中 4已完成 5已取消',
`total_amount` decimal(10,2) NOT NULL,
`actual_amount` decimal(10,2) NOT NULL,
`pay_time` datetime DEFAULT NULL,
`complete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_shop_id` (`shop_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 菜品表设计建议加入全文索引
CREATE TABLE `food` (
`id` bigint NOT NULL AUTO_INCREMENT,
`shop_id` bigint NOT NULL,
`name` varchar(100) NOT NULL,
`description` text,
`price` decimal(10,2) NOT NULL,
`month_sales` int DEFAULT '0',
`tags` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_shop_id` (`shop_id`),
FULLTEXT KEY `ft_idx_name_desc` (`name`,`description`) -- 支持菜品搜索
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
踩坑提醒:外卖平台的订单状态流转一定要设计好版本号或乐观锁,防止并发修改导致状态混乱。我们曾经因为没加版本控制,出现过订单重复结算的严重问题。
3. 核心功能实现
3.1 订单创建流程优化
订单创建是系统最核心也是最容易出问题的环节。我们的实现方案:
python复制# orders/views.py
class OrderCreateView(APIView):
def post(self, request):
with transaction.atomic(): # 开启事务
# 1. 验证购物车
cart_items = CartService.validate_cart(request.user)
# 2. 生成订单号(雪花算法)
order_no = generate_order_no()
# 3. 创建订单主表
order = Order.objects.create(
user=request.user,
order_no=order_no,
total_amount=sum(item['price']*item['quantity'] for item in cart_items),
status=Order.Status.PENDING
)
# 4. 创建订单明细
OrderItem.objects.bulk_create([
OrderItem(
order=order,
food_id=item['food_id'],
quantity=item['quantity'],
price=item['price']
) for item in cart_items
])
# 5. 清空购物车
CartService.clear_cart(request.user)
# 6. 发送创建事件
order_created.send(sender=self.__class__, order=order)
return Response({'order_no': order_no}, status=201)
关键优化点:
- 使用数据库事务确保数据一致性
- 批量插入订单明细(性能提升5倍+)
- 采用信号机制解耦后续处理
- 订单号使用雪花算法避免重复
3.2 支付对接实战
微信支付对接有几个容易踩的坑:
python复制# payment/services.py
class WechatPayService:
@staticmethod
def create_payment(order_no, amount, openid):
# 统一下单接口
params = {
'body': '校园外卖订单',
'out_trade_no': order_no,
'total_fee': int(amount * 100), # 单位是分
'openid': openid,
'trade_type': 'JSAPI',
'notify_url': settings.WECHAT_PAY_NOTIFY_URL
}
try:
resp = WxPay.unified_order(params)
if resp['return_code'] == 'SUCCESS':
# 组装前端需要的支付参数
pay_params = {
'appId': settings.WECHAT_APPID,
'timeStamp': str(int(time.time())),
'nonceStr': generate_nonce_str(),
'package': f"prepay_id={resp['prepay_id']}",
'signType': 'MD5'
}
pay_params['paySign'] = generate_sign(pay_params)
return pay_params
raise PaymentError(resp['return_msg'])
except Exception as e:
logger.error(f'微信支付创建失败: {str(e)}')
raise PaymentError('支付系统繁忙')
重要提醒:支付回调接口一定要做好幂等处理!我们遇到过因网络问题导致重复回调,如果不做校验会导致重复记账。
4. 性能优化方案
4.1 缓存策略设计
python复制# cache/decotators.py
def cache_response(key_prefix, timeout=300):
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
cache_key = f"{key_prefix}:{request.user.id if request.user.is_authenticated else 'anon'}"
if 'no_cache' in request.GET:
return view_func(request, *args, **kwargs)
data = cache.get(cache_key)
if data is not None:
return JsonResponse(json.loads(data))
response = view_func(request, *args, **kwargs)
if response.status_code == 200:
cache.set(cache_key, response.content, timeout)
return response
return _wrapped_view
return decorator
# 使用示例
@cache_response('shop_list')
def shop_list(request):
shops = Shop.objects.filter(is_active=True).select_related('category')
serializer = ShopSerializer(shops, many=True)
return Response(serializer.data)
缓存策略要点:
- 按用户区分缓存(登录用户和访客看到的内容可能不同)
- 支持强制刷新机制(通过no_cache参数)
- 使用select_related减少数据库查询
- 对分页结果进行整体缓存
4.2 数据库查询优化
几个特别有效的优化手段:
- 延迟加载大字段:
python复制class Food(models.Model):
# 基础字段
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
# 大字段单独配置
description = models.TextField()
class Meta:
defer = ('description',) # 默认不加载
- 使用annotate替代多个查询:
python复制from django.db.models import Count, Sum
def shop_detail(request, shop_id):
shop = Shop.objects.annotate(
food_count=Count('foods'),
monthly_sales=Sum('foods__month_sales')
).get(pk=shop_id)
- 索引优化技巧:
- 为所有外键添加索引
- 状态字段建立组合索引(如(status, create_time))
- 使用EXPLAIN分析慢查询
5. 部署与监控
5.1 Docker化部署方案
dockerfile复制# backend/Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000"]
yaml复制# docker-compose.prod.yml
version: '3.8'
services:
backend:
build: ./backend
ports:
- "8000:8000"
env_file: .env.prod
depends_on:
- redis
- db
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "80:80"
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
db_data:
部署要点:
- 使用多阶段构建减小镜像体积
- 环境变量与代码分离
- 数据库数据持久化
- 生产环境使用Nginx反向代理
5.2 监控告警配置
Prometheus的监控指标示例:
python复制# monitoring/middleware.py
class MetricsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.request_counter = Counter(
'django_http_requests_total',
'Total HTTP Requests',
['method', 'path', 'status']
)
self.request_latency = Histogram(
'django_http_request_latency_seconds',
'HTTP Request Latency',
['method', 'path']
)
def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
latency = time.time() - start_time
self.request_counter.labels(
method=request.method,
path=request.path,
status=response.status_code
).inc()
self.request_latency.labels(
method=request.method,
path=request.path
).observe(latency)
return response
告警规则配置示例:
yaml复制# prometheus/alerts.yml
groups:
- name: django
rules:
- alert: HighErrorRate
expr: rate(django_http_requests_total{status=~"5.."}[5m]) / rate(django_http_requests_total[5m]) > 0.05
for: 10m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.path }}"
description: "5xx error rate is {{ $value }}"
6. 项目经验总结
在开发校园外卖平台过程中,有几个关键经验值得分享:
-
分布式事务处理:当订单服务需要同时操作订单表、库存表和支付记录时,我们最终采用了Saga模式+本地消息表的方案,比单纯的分布式事务性能更好。
-
压测发现的瓶颈:在模拟500并发下单时,发现购物车锁竞争严重。解决方案是将购物车数据拆分为基础信息(MySQL)和实时数据(Redis)。
-
移动端适配技巧:Vue项目中使用vw/vh单位配合postcss-px-to-viewport插件,可以完美适配不同尺寸的手机屏幕。
-
安全防护要点:
- 接口防刷:使用redis实现令牌桶限流
- XSS防护:前端用vue-sanitize,后端用django-bleach
- SQL注入:坚持使用ORM或参数化查询
-
开发效率提升:
- 使用apifox管理接口文档和mock数据
- 配置热更新的开发环境(前端vite+后端django-runserver)
- 编写自动化测试脚本(特别是支付相关流程)
这个项目从技术选型到最终上线历时3个月,期间遇到了各种预料之外的问题,但最终的成果证明Python+Vue的全栈组合确实能高效构建复杂的校园外卖平台。对于想要学习全栈开发的同学,这个项目涵盖了前后端分离、数据库设计、性能优化等核心知识点,是非常好的练手项目。