1. 项目背景与核心价值
校园外卖点餐平台是当前高校场景下的刚需应用。根据我在多个高校的实地调研,学生群体对外卖服务的需求呈现几个典型特征:高峰期集中(午间11:30-13:00、晚间17:00-19:00)、配送范围精确(需精确到宿舍楼栋)、支付方式灵活(需支持校园卡绑定)。传统外卖平台在这些场景下往往存在配送费过高、定位不准等问题。
这个毕设项目采用SpringBoot+微信小程序的组合方案,完美契合校园场景的技术需求。微信小程序无需安装的特性,使学生打开即用;SpringBoot的后端稳定性,能应对用餐高峰期的并发请求;MySQL的关系型数据管理,则适合处理订单、用户等结构化数据。
提示:选择校园外卖作为毕设课题时,建议优先考虑本校的特殊需求。例如有的学校禁止外卖员入校,就需要设计自提柜功能;有的学校有多个食堂,就需要区分不同商户的接单逻辑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计解析
2.1 整体技术栈选型
本系统采用经典的三层架构:
- 前端:微信小程序(WXML+WXSS+JS)
- 后端:SpringBoot 2.7.x + MyBatis-Plus
- 数据库:MySQL 8.0
- 辅助工具:Lombok(简化POJO)、Hutool(工具包)、Swagger(API文档)
选择这套组合主要基于:
- 微信小程序日活超过4亿,校园覆盖率近100%,无需考虑兼容性问题
- SpringBoot的自动配置特性,让毕业生能快速搭建RESTful API
- MySQL的事务支持,确保订单状态变更的原子性
2.2 核心业务流程设计
订单生成的关键路径如下:
java复制// 伪代码示例
@Transactional
public Order createOrder(OrderDTO dto) {
// 1. 校验库存
Dish dish = dishService.getById(dto.getDishId());
if(dish.getStock() < dto.getQuantity()) {
throw new BusinessException("库存不足");
}
// 2. 扣减库存
dishService.reduceStock(dto.getDishId(), dto.getQuantity());
// 3. 生成订单
Order order = new Order();
BeanUtils.copyProperties(dto, order);
order.setOrderNo(IdUtil.getSnowflakeNextIdStr()); // 雪花算法生成订单号
orderMapper.insert(order);
// 4. 发送延时消息检查未支付订单
rabbitTemplate.convertAndSend(
"order.delay.exchange",
"order.delay.routingkey",
order.getOrderNo(),
message -> {
message.getMessageProperties().setDelay(15 * 60 * 1000); // 15分钟超时
return message;
}
);
return order;
}
3. 数据库关键表设计
3.1 核心表结构
sql复制-- 商户表
CREATE TABLE `merchant` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '商户名称',
`address` varchar(255) NOT NULL COMMENT '详细地址',
`longitude` decimal(10,7) DEFAULT NULL COMMENT '经度',
`latitude` decimal(10,7) DEFAULT NULL COMMENT '纬度',
`business_hours` varchar(100) DEFAULT NULL COMMENT '营业时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 菜品表
CREATE TABLE `dish` (
`id` bigint NOT NULL AUTO_INCREMENT,
`merchant_id` bigint NOT NULL,
`name` varchar(100) NOT NULL,
`price` decimal(10,2) NOT NULL,
`stock` int DEFAULT '0' COMMENT '库存',
`image_url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_merchant` (`merchant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 订单表(分库分表场景需额外设计)
CREATE TABLE `order` (
`id` bigint NOT NULL,
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint NOT NULL,
`merchant_id` bigint NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-待支付 1-已支付 2-已取消',
`delivery_address` varchar(255) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user` (`user_id`),
KEY `idx_merchant` (`merchant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 索引优化建议
- 订单表按user_id分片:校园场景下,学生查自己订单的频率远高于其他操作
- 为status字段添加条件索引:WHERE status=0 AND create_time > ?
- 使用覆盖索引避免回表:
sql复制ALTER TABLE `order` ADD INDEX `idx_user_status` (`user_id`, `status`);
4. 微信小程序端关键实现
4.1 地图选址组件优化
校园场景需要特殊处理地图选址:
javascript复制// pages/address/address.js
Page({
data: {
markers: [{
id: 0,
latitude: 39.908823, // 学校中心坐标
longitude: 116.397470,
iconPath: '/images/location.png',
width: 30,
height: 30
}],
// 限制拖动范围
includePoints: [{
latitude: 39.900000,
longitude: 116.390000
}, {
latitude: 39.920000,
longitude: 116.410000
}]
},
// 只允许选择宿舍楼
onChooseLocation(e) {
const { latitude, longitude } = e.detail
if (!this.isInDormitoryArea(latitude, longitude)) {
wx.showToast({ title: '请选择宿舍区域', icon: 'none' })
return
}
this.setData({ selectedLocation: e.detail })
}
})
4.2 性能优化方案
- 图片懒加载:
xml复制<image lazy-load src="{{dish.imageUrl}}" mode="aspectFill" /> - 使用分包加载:
json复制{ "subPackages": [{ "root": "packageA", "pages": ["pages/dormitory", "pages/canteen"] }] } - 数据预加载:
javascript复制// app.js App({ onLaunch() { wx.preload({ url: '/api/dish/hot', success(res) { getApp().globalData.hotDishes = res.data } }) } })
5. 典型问题排查实录
5.1 微信支付签名失败
错误现象:调用wx.requestPayment时提示"签名验证失败"
排查步骤:
- 检查统一下单接口返回的prepay_id是否有效
- 确认参与签名的参数顺序严格遵循:
java复制Map<String, String> params = new TreeMap<>(); params.put("appId", appId); params.put("timeStamp", timestamp); params.put("nonceStr", nonceStr); params.put("package", "prepay_id=" + prepayId); params.put("signType", "MD5"); - 验证商户API密钥是否与微信商户平台一致
- 使用官方签名校验工具比对:
bash复制curl https://api.mch.weixin.qq.com/pay/sandbox/signverify \ -d "<xml><sign_type>MD5</sign_type>...</xml>"
5.2 高并发下的超卖问题
解决方案对比:
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 乐观锁 | UPDATE dish SET stock=stock-1 WHERE id=? AND stock>=1 | 实现简单 | 失败率高 |
| Redis原子操作 | DECR + WATCH | 性能好 | 需维护缓存一致性 |
| 分布式锁 | RedissonLock | 可靠性高 | 性能损耗 |
最终采用Redis Lua脚本方案:
lua复制local key = KEYS[1]
local change = tonumber(ARGV[1])
local stock = tonumber(redis.call('GET', key))
if stock >= change then
redis.call('DECRBY', key, change)
return 1
else
return 0
end
6. 项目扩展方向建议
-
智能推荐系统:
- 基于协同过滤算法推荐菜品
python复制# 伪代码示例 from surprise import Dataset, KNNBasic data = Dataset.load_builtin('ml-100k') algo = KNNBasic() algo.fit(data.build_full_trainset()) -
配送路径优化:
- 使用Dijkstra算法计算最短路径
java复制public List<Building> findShortestPath(Building start, Building end) { // 构建校园路网图 Graph graph = buildCampusGraph(); return Dijkstra.findPath(graph, start, end); } -
语音点餐功能:
- 集成微信同声传译插件
javascript复制const plugin = requirePlugin('WechatSI') plugin.translate({ lfrom: 'zh_CN', lto: 'en_US', content: '我要一份宫保鸡丁' })
我在实际开发中发现,校园外卖系统最需要关注的是极端场景下的稳定性。例如军训期间中午12点下课时,瞬时流量可能是平日的10倍以上。建议在压力测试阶段,使用JMeter模拟以下场景:
code复制Thread Group: 500线程
Ramp-up: 60秒
Loop Count: 永远
HTTP Request: /api/order/create
通过这个项目,可以系统掌握从需求分析、技术选型到性能调优的全流程开发经验。特别是微信小程序与SpringBoot的交互设计,对理解现代Web开发架构非常有帮助。
