1. 项目概述
私房菜上门定制系统是一个基于SpringBoot框架开发的O2O餐饮服务平台,主要解决家庭厨房资源闲置和个性化餐饮需求之间的矛盾。这个系统我花了半年时间从零开始搭建,期间踩过不少坑,也积累了一些值得分享的经验。
系统核心功能包括:厨师端入驻审核、用户在线预约、菜单个性化定制、地理位置服务、在线支付和评价体系。相比传统的外卖平台,我们更注重"私人定制"这个概念——用户不仅可以预约厨师上门服务,还能根据自身口味、饮食禁忌等需求定制专属菜单。
技术选型上,后端采用SpringBoot 2.7 + MyBatis Plus的组合,前端使用Vue3+Element Plus,数据库是MySQL 8.0,配合Redis做缓存。选择这套技术栈主要考虑三点:一是SpringBoot的快速开发特性非常适合创业初期快速迭代;二是Vue3的Composition API写起来更顺手;三是MySQL+Redis的组合足够应对初期流量,且运维成本低。
2. 系统架构设计
2.1 整体架构解析
系统采用经典的三层架构,但在细节上做了些优化:
code复制表现层(Web) → 业务逻辑层(Service) → 数据访问层(DAO)
↑ ↑
| |
API网关 消息队列
特别要说明的是,我们在业务层和数据层之间加入了消息队列(RabbitMQ),主要处理订单状态变更和通知类消息。比如当用户下单后,系统会通过消息队列异步发送短信通知,避免同步等待导致接口响应变慢。
提示:消息队列的引入时机很重要。初期我们曾把所有通知都做成同步的,结果高峰期接口响应时间飙升到3秒以上。后来通过压测发现,把非核心路径改为异步后,接口平均响应时间降到了800ms左右。
2.2 数据库设计要点
数据库设计有几个关键点值得分享:
-
厨师表(chef):除了基础信息,特别设计了
certification_status字段记录认证状态(0-未认证 1-审核中 2-已认证),以及service_range多边形字段存储服务范围 -
订单表(order):采用纵表设计,主表存订单基础信息,子表存菜品明细。这样设计是因为私房菜订单经常会有临时调整,纵表更方便记录变更历史
-
菜单模板(menu_template):使用JSON类型字段存储菜品成分和过敏原信息,方便前端灵活展示
sql复制CREATE TABLE `chef` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '关联用户ID',
`certification_status` tinyint NOT NULL DEFAULT '0',
`service_range` polygon DEFAULT NULL COMMENT '服务范围多边形',
`specialties` varchar(255) DEFAULT NULL COMMENT '擅长菜系',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.3 安全设计考量
餐饮系统涉及金钱交易和个人信息,安全方面我们做了多重防护:
- 接口层面:Spring Security + JWT实现鉴权,敏感操作(如支付)需要二次验证
- 数据层面:用户密码加盐哈希存储,支付信息加密入库
- 运维层面:所有接口都有防刷限流,关键业务表开启Binlog审计
3. 核心功能实现
3.1 厨师入驻流程
厨师入驻是系统的核心功能之一,实现上主要分为四个步骤:
- 资质上传:支持身份证、健康证、厨艺证书等材料上传(使用阿里云OSS存储)
- 人脸核验:调用第三方API进行活体检测
- 服务范围设置:基于高德地图API绘制服务多边形区域
- 审核队列:后台管理员审核通过后,厨师状态变更为可用
这里有个性能优化点:最初我们直接存储地图截图,后来改为存储多边形坐标点,存储空间减少了90%,而且更方便后续的距离计算。
3.2 预约下单流程
预约功能的技术实现有几个关键点:
-
时间冲突检测:检查厨师在选定时间段是否已有预约
java复制public boolean checkTimeConflict(Long chefId, LocalDateTime start, LocalDateTime end) { return orderMapper.selectCount(new QueryWrapper<Order>() .eq("chef_id", chefId) .ge("service_time", start) .le("service_time", end) .notIn("status", Arrays.asList(CANCELED, COMPLETED))) > 0; } -
菜单定制:采用组合模式设计菜单结构,支持菜品、套餐的多级嵌套
-
价格计算:基础服务费 + 菜品费用 + 交通补贴(根据距离动态计算)
3.3 支付系统集成
支付模块我们对接了支付宝和微信支付双渠道,技术实现上有几个注意事项:
- 采用策略模式封装不同支付渠道,方便后续扩展
- 支付结果回调要做签名验证和幂等处理
- 本地事务与第三方支付的协调(下面会详细说明)
4. 关键技术难点
4.1 分布式事务处理
私房菜系统最复杂的业务场景是"下单+支付"的分布式事务。我们的解决方案是:
- 本地创建订单状态为"待支付"
- 调用支付接口,如果失败则直接更新订单状态
- 支付成功但本地更新失败时,通过定时任务补偿
java复制@Transactional
public OrderResult createOrder(OrderRequest request) {
// 1. 创建本地订单
Order order = buildOrder(request);
orderMapper.insert(order);
// 2. 调用支付
PaymentResult payment = paymentService.create(order);
if (!payment.isSuccess()) {
order.setStatus(OrderStatus.FAILED);
orderMapper.updateById(order);
throw new BusinessException("支付创建失败");
}
// 3. 更新订单为已支付
order.setPaymentSn(payment.getSn());
order.setStatus(OrderStatus.PAID);
orderMapper.updateById(order);
// 发送领域事件
applicationEventPublisher.publishEvent(new OrderPaidEvent(this, order));
return convertToResult(order);
}
4.2 地理位置服务
厨师服务范围匹配是个技术难点,我们采用MySQL的空间函数实现:
sql复制SELECT id FROM chef
WHERE ST_Contains(service_range, POINT(#{lng}, #{lat}))
AND certification_status = 2
为了提高查询效率,我们在service_range字段上建立了空间索引,查询速度从原来的1200ms优化到了200ms以内。
4.3 实时通知系统
通知系统经历了三次迭代:
- 第一版:直接调用短信接口,高峰期经常超时
- 第二版:引入Redis队列,但存在消息丢失风险
- 最终版:RabbitMQ + 本地消息表,确保消息必达
5. 部署与运维
5.1 服务器配置建议
根据我们的经验,初期部署建议如下配置:
| 服务 | 配置 | 数量 | 备注 |
|---|---|---|---|
| 应用服务器 | 4核8G | 2 | 建议Docker部署 |
| MySQL | 8核16G SSD 500G | 1 | 主从架构 |
| Redis | 4G内存 | 1 | 持久化开启 |
| RabbitMQ | 4核8G | 1 | 磁盘空间建议100G以上 |
5.2 容器化部署
我们使用Docker Compose编排服务,关键配置如下:
yaml复制version: '3'
services:
app:
image: private-chef:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
volumes:
- ./mysql/data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=yourpassword
redis:
image: redis:6
command: redis-server --appendonly yes
volumes:
- ./redis/data:/data
5.3 监控与日志
推荐使用Prometheus + Grafana监控系统健康状态,关键指标包括:
- 接口响应时间(特别是支付和下单接口)
- 数据库连接池使用率
- Redis内存使用情况
- 消息队列积压情况
日志收集采用ELK方案,注意要对敏感信息(如身份证号、支付账号)进行脱敏处理。
6. 常见问题排查
6.1 支付回调失败
现象:用户已付款但订单状态未更新
排查步骤:
- 检查支付回调日志是否收到请求
- 验证签名是否正确
- 检查本地事务是否提交成功
- 查看补偿任务是否正常运行
解决方案:我们开发了一个可视化工具来手动修复状态不一致的订单。
6.2 地图服务异常
现象:厨师设置服务范围时地图加载不出来
可能原因:
- 高德地图AK配置错误
- 前端JS文件加载失败
- 浏览器跨域问题
解决方案:我们在后台增加了AK有效性检测功能,提前发现问题。
6.3 性能瓶颈优化
随着用户量增长,我们遇到了几个性能问题:
-
订单查询慢:通过添加复合索引解决
sql复制ALTER TABLE `order` ADD INDEX idx_chef_status_time (`chef_id`, `status`, `service_time`); -
厨师列表加载慢:引入缓存,设置5分钟过期时间
java复制@Cacheable(value = "chefList", key = "#areaCode") public List<ChefVO> getChefListByArea(String areaCode) { // 查询逻辑 } -
通知延迟:增加RabbitMQ消费者数量,从2个提高到8个
7. 二次开发建议
如果你想基于这个系统进行二次开发,我有几个建议:
-
扩展方向:
- 增加直播做菜功能(需集成腾讯云直播SDK)
- 开发会员体系(成长值、积分兑换)
- 添加智能推荐算法(基于用户口味推荐厨师)
-
代码结构优化:
- 将通用组件抽离到单独的module
- 使用DDD重构领域模型
- 增加单元测试覆盖率(目前只有60%)
-
性能优化:
- 考虑分库分表(订单表按月分表)
- 引入Elasticsearch优化搜索
- 尝试Service Mesh架构解耦服务
这套系统从开发到上线运营,我最大的体会是:技术方案没有最好只有最合适。初期我们追求技术先进性,后来发现稳定性和开发效率才是创业项目更需要的。比如最初我们用MongoDB存订单,后来因为事务问题又迁回MySQL,这个教训告诉我们,选型时要充分考虑业务特性的刚性需求。