1. 项目概述
校园外卖系统作为连接学生与周边餐饮商家的桥梁,在高校环境中扮演着越来越重要的角色。不同于社会化的外卖平台,校园场景具有明显的特殊性:用户群体集中、用餐时间固定、配送范围有限且存在校园管理限制。这些特点决定了通用外卖平台在校园环境中往往存在"水土不服"的问题。
我最近完成了一个基于SpringBoot的校园外卖系统开发项目,这个系统从零开始构建,完整实现了从菜品展示、在线下单、订单处理到配送管理的全流程。系统采用B/S架构,前端使用Vue.js+ElementUI,后端基于SpringBoot+MyBatis技术栈,数据库选用MySQL 8.0,并整合了Redis作为缓存层。整个开发周期约2个月,目前已在校内测试环境稳定运行。
2. 系统架构设计
2.1 技术选型考量
后端选择SpringBoot框架主要基于以下几个考量点:
- 快速开发:SpringBoot的自动配置和起步依赖大大减少了配置工作量
- 微服务友好:为未来可能的服务拆分预留了扩展空间
- 生态丰富:可以方便地整合MyBatis、Redis、RabbitMQ等常用组件
- 社区支持:Spring生态拥有庞大的开发者社区和丰富的学习资源
数据库选型时,考虑到校园外卖系统的数据特点:
- 数据结构相对规整
- 事务一致性要求中等
- 读写比例约为7:3
- 数据量在单机可处理范围内
因此选择了成熟稳定的MySQL 8.0,其JSON类型支持也为未来可能的灵活扩展提供了便利。
2.2 系统分层架构
系统采用经典的三层架构设计:
code复制表现层:Vue.js + ElementUI
↓ (RESTful API)
业务逻辑层:SpringBoot + Spring Security
↓ (DAO接口)
数据访问层:MyBatis + MySQL
补充的组件包括:
- Redis:缓存热点数据和会话信息
- RabbitMQ:处理异步任务如订单超时检查
- Alibaba Cloud OSS:存储菜品图片等静态资源
- 微信支付SDK:集成校园卡和小程序支付
2.3 数据库设计要点
数据库设计遵循了以下原则:
- 适度冗余:在订单相关表中冗余了用户和菜品信息,避免多表关联查询
- 读写分离:将高频查询的表如菜品信息与低频更新的表如商家信息分开设计
- 状态机明确:订单状态使用枚举类型,确保状态流转清晰可控
核心表关系如下:
code复制用户表(user) ← 订单表(order)
↑
商家表(merchant) → 菜品表(dish)
3. 核心功能实现
3.1 多角色权限控制
系统采用RBAC(基于角色的访问控制)模型,定义了四种角色:
-
普通学生:
- 菜品浏览与搜索
- 下单与支付
- 订单跟踪与评价
-
商家用户:
- 菜品管理(CRUD)
- 订单处理(接单/拒单)
- 营业数据统计
-
配送员:
- 接单与配送状态更新
- 配送路线规划
- 收入统计
-
管理员:
- 用户管理
- 系统配置
- 数据监控
权限控制通过Spring Security实现,关键配置如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/student/**").hasRole("STUDENT")
.antMatchers("/api/merchant/**").hasRole("MERCHANT")
.antMatchers("/api/delivery/**").hasRole("DELIVERY")
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
3.2 订单状态机设计
订单生命周期管理是系统的核心难点,我们设计了一个明确的状态机:
code复制待支付 → 已支付 → 商家接单 → 配送中 → 已完成
↓ ↓
超时取消 用户取消/商家拒单
状态转换通过状态模式实现,确保业务逻辑清晰:
java复制public interface OrderState {
void pay(Order order);
void cancel(Order order);
void merchantAccept(Order order);
// 其他状态方法...
}
@Component
@Scope("prototype")
public class PaidState implements OrderState {
@Override
public void cancel(Order order) {
if(order.getCreateTime().plusMinutes(30).isBefore(LocalDateTime.now())) {
order.setState(OrderStatus.CANCELLED);
// 退款逻辑...
} else {
throw new IllegalStateException("订单已超时,不能直接取消");
}
}
// 其他方法实现...
}
3.3 高并发场景处理
针对用餐高峰期的并发问题,我们采取了以下措施:
- 菜品库存采用Redis原子操作:
java复制public boolean reduceStock(Long dishId, int quantity) {
String key = "dish:stock:" + dishId;
long value = redisTemplate.opsForValue().increment(key, -quantity);
if (value >= 0) {
return true;
} else {
// 库存不足,回滚
redisTemplate.opsForValue().increment(key, quantity);
return false;
}
}
- 订单创建使用分布式锁:
java复制public String createOrder(OrderDTO orderDTO) {
String lockKey = "order:lock:" + orderDTO.getUserId();
try {
// 尝试获取锁,最多等待3秒,锁有效期30秒
boolean locked = redisLock.tryLock(lockKey, 3, 30, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("操作太频繁,请稍后再试");
}
// 创建订单逻辑...
} finally {
redisLock.unlock(lockKey);
}
}
- 使用消息队列削峰:
java复制@RabbitListener(queues = "order.queue")
public void processOrder(OrderMessage message) {
// 异步处理订单逻辑
}
4. 特色功能实现
4.1 智能推荐系统
基于用户历史订单数据,实现了个性化推荐:
- 协同过滤算法:
python复制# 使用Surprise库实现
from surprise import KNNBasic
from surprise import Dataset
data = Dataset.load_builtin('ml-100k')
trainset = data.build_full_trainset()
sim_options = {'name': 'cosine', 'user_based': False}
algo = KNNBasic(sim_options=sim_options)
algo.fit(trainset)
- 实时推荐接口:
java复制@GetMapping("/recommendations")
public List<DishVO> getRecommendations(@RequestParam Long userId) {
// 1. 从缓存获取推荐结果
// 2. 缓存不存在则调用Python服务计算
// 3. 返回推荐菜品列表
}
4.2 配送路径优化
结合校园地图数据,实现了配送路径优化算法:
java复制public List<DeliveryTask> optimizeRoute(List<Order> orders) {
// 1. 聚类分析订单位置
// 2. 使用Dijkstra算法计算最优路径
// 3. 分配配送任务
}
4.3 数据统计分析
为商家和管理员提供了丰富的数据看板:
- 使用Elasticsearch存储订单数据
- 通过Kibana可视化分析
- 定时生成经营报表
5. 开发经验与优化建议
5.1 踩过的坑
-
订单超时问题:
初始设计使用数据库轮询检查超时订单,导致性能瓶颈。后改为Redis过期键+消息队列方案。 -
分布式事务:
跨服务的订单创建和库存扣减最初没有考虑事务一致性,后引入Seata解决。 -
缓存穿透:
针对恶意请求不存在的菜品ID,采用布隆过滤器进行过滤。
5.2 性能优化
- SQL优化:
sql复制-- 原查询
SELECT * FROM orders WHERE user_id = ? AND status = ?;
-- 优化后
SELECT id, order_no, status FROM orders
WHERE user_id = ? AND status = ?
ORDER BY create_time DESC LIMIT 20;
-
接口响应时间从平均800ms降低到200ms内
-
服务器配置从4核8G缩减到2核4G,节省40%成本
5.3 安全实践
- 密码加密:
java复制public class PasswordEncoder {
public static String encode(String rawPassword) {
return BCrypt.hashpw(rawPassword, BCrypt.gensalt());
}
public static boolean matches(String rawPassword, String encodedPassword) {
return BCrypt.checkpw(rawPassword, encodedPassword);
}
}
- XSS防护:
java复制@Bean
public FilterRegistrationBean<XssFilter> xssFilter() {
FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
return registration;
}
- 定期安全扫描:
集成OWASP ZAP进行自动化安全测试
6. 部署与监控
6.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: campus-food:1.0
ports:
- "8080:8080"
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:6.0
6.2 监控方案
- Prometheus + Grafana监控系统指标
- ELK收集和分析日志
- 自定义业务指标埋点
7. 项目总结
这个校园外卖系统项目让我深刻理解了如何将一个业务需求转化为技术实现的全过程。最大的收获不是技术层面的,而是学会了如何在各种约束条件下做出合理的技术决策。
几点特别的心得体会:
- 不要过度设计:初期总想用最"高级"的技术,后来发现简单可靠的方案往往更合适
- 监控要先行:等出了问题再加监控就太晚了
- 文档同样重要:好的文档能极大降低维护成本
系统目前运行稳定,日均处理订单约3000单,高峰期QPS达到200。未来计划加入智能调度、人脸识别取餐等创新功能,进一步提升用户体验。