1. 项目概述
校园外卖服务系统是一个典型的互联网O2O应用,采用前后端分离架构实现。系统主要解决校园内师生订餐不便、商家管理混乱、配送效率低下等问题。我在实际开发中发现,这种系统对并发性能和用户体验的要求比普通电商系统更高,因为用餐高峰期会出现明显的流量峰值。
技术栈选择上,后端采用SpringBoot 2.7 + MyBatis 3.5,前端使用Vue3 + Element Plus,数据库采用MySQL 8.0。这套组合在校园场景下表现优异:SpringBoot的自动配置简化了微服务部署,Vue3的Composition API让复杂订单状态管理更清晰,而MySQL的窗口函数则大大简化了商家销售统计的实现。
2. 核心模块设计
2.1 用户端功能实现
用户认证采用JWT+Redis的方案。这里有个细节:校园场景需要区分学生/教师/商户三种身份类型,我在JWT的claims中增加了userType字段。前端路由守卫根据这个字段动态生成菜单:
java复制// JWT生成逻辑
String token = Jwts.builder()
.claim("userId", user.getId())
.claim("userType", user.getType()) // 1学生 2教师 3商户
.signWith(SECRET_KEY)
.compact();
订单模块采用状态机模式设计,包含以下状态流转:
code复制待支付 -> 已支付 -> 备餐中 -> 配送中 -> 已完成
↘ 已取消 ↗
2.2 商户端关键技术
菜品管理使用MyBatis的动态SQL实现多条件查询。这里有个性能优化点:当菜品图片较多时,应该使用OSS存储而非数据库BLOB。我在application.yml中这样配置阿里云OSS:
yaml复制aliyun:
oss:
endpoint: https://oss-cn-hangzhou.aliyuncs.com
access-key-id: ${ACCESS_KEY}
access-key-secret: ${SECRET}
bucket-name: campus-food
2.3 配送调度算法
校园场景下的配送有其特殊性:配送员通常步行或骑自行车,且需要兼顾多个楼宇。我实现了一个基于贪心算法的智能调度:
java复制public List<Order> assignDeliveries(List<Order> orders) {
// 按楼宇聚类
Map<String, List<Order>> buildingGroups = orders.stream()
.collect(Collectors.groupingBy(Order::getBuilding));
// 每个楼宇按时间窗排序
return buildingGroups.values().stream()
.flatMap(group -> group.stream()
.sorted(Comparator.comparing(Order::getCreateTime)))
.collect(Collectors.toList());
}
3. 数据库设计要点
3.1 表结构优化
主表包括:
- user(用户)
- shop(商户)
- food(菜品)
- order(订单)
- order_detail(订单明细)
特别注意订单表的索引设计:
sql复制CREATE INDEX idx_order_user ON `order`(user_id);
CREATE INDEX idx_order_shop ON `order`(shop_id);
CREATE INDEX idx_order_status ON `order`(status);
3.2 分库分表策略
考虑到校园场景的数据量,我采用按学年水平分表。使用ShardingSphere配置:
yaml复制spring:
shardingsphere:
datasource:
names: ds0
sharding:
tables:
order:
actual-data-nodes: ds0.order_$->{2022..2023}
table-strategy:
inline:
sharding-column: create_time
algorithm-expression: order_$->{create_time.getYear()}
4. 前后端交互规范
4.1 API设计原则
采用RESTful风格,但针对校园场景做了调整:
- GET /api/v1/shops?building=A 获取A楼宇的商家
- POST /api/v1/orders/precreate 预创建订单(计算配送费)
响应统一格式:
json复制{
"code": 200,
"data": {},
"message": "success"
}
4.2 WebSocket实时通知
订单状态变更通过WebSocket推送。前端实现:
javascript复制// 在Vue3 setup中
const socket = new WebSocket(`wss://api.example.com/ws?token=${token}`)
socket.onmessage = (event) => {
const msg = JSON.parse(event.data)
if(msg.type === 'ORDER_UPDATE') {
updateOrderStatus(msg.data)
}
}
5. 部署实践
5.1 容器化部署
使用Docker Compose编排服务:
dockerfile复制# backend Dockerfile
FROM openjdk:11
COPY target/campus-food.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
yaml复制# docker-compose.yml
version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
backend:
build: ./backend
ports:
- "8080:8080"
frontend:
image: nginx
ports:
- "80:80"
5.2 性能调优
针对校园用餐高峰的配置建议:
properties复制# SpringBoot配置
server.tomcat.max-threads=200
spring.datasource.hikari.maximum-pool-size=20
# Redis缓存配置
spring.cache.type=redis
spring.redis.timeout=3000
6. 踩坑记录
-
MyBatis批量插入性能问题
初期使用foreach拼接SQL导致性能低下,改用BatchExecutor:java复制@Bean public Executor executor(DataSource dataSource) { SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory(dataSource)); template.getConfiguration().setDefaultExecutorType(ExecutorType.BATCH); return template; } -
Vue3响应式丢失
在解构props时会丢失响应性,需要使用toRefs:javascript复制const { order } = toRefs(props) watch(order, (newVal) => {...}) -
MySQL时区问题
必须统一设置时区:yaml复制spring: datasource: url: jdbc:mysql://localhost:3306/campus_food?serverTimezone=Asia/Shanghai
这个项目让我深刻体会到校园场景的特殊性:需要处理明显的时段性流量波动,对地理位置信息敏感,且用户群体行为模式高度一致。建议后续可以加入智能推荐算法,根据历史订单预测用户偏好
