☰
上门做饭小程序搭建:档期管理与订单流转方案
2026/9/30 5:28:47 网站建设 项目流程

上门做饭小程序搭建:档期管理与订单流转方案

上门做饭小程序属于同城本地履约类系统,和普通电商最大差异在于服务依赖厨师的时间资源。用户下单本质是预约厨师特定时间段的服务,而不是实物商品。很多项目开发时,直接套用电商订单逻辑,忽略档期资源锁定,容易出现同一时段厨师被多次下单、订单状态错乱、取消订单后档期无法释放等问题,造成大量客诉。本文基于SpringBoot搭建小程序后端,围绕档期资源模型、订单状态机、全链路流转逻辑展开,附带Java核心代码和数据库设计,给出一套可落地的实现方案。

一、业务场景与开发痛点

1.1 业务场景概述

整套小程序分为用户端、厨师端、平台管理端。

  • 用户端:浏览厨师信息、查看厨师可预约档期、选择预约日期和时段、填写地址与菜品需求、提交订单支付、查看订单进度、服务完成后评价。
  • 厨师端:设置可接单日期与时段、查看预约订单、接单确认、上门履约、完成服务。
  • 平台端:管理厨师账号、查看档期占用情况、监控订单流转、处理售后异常。

核心业务链路:厨师维护档期 → 用户查询可用时段 → 锁定档期创建订单 → 用户支付 → 厨师接单履约 → 服务完成/订单取消,档期资源释放。

1.2 常见开发痛点

  1. 档期模型设计粗糙,仅记录日期,没有拆分时段,无法支持午市、晚市分开预约,容易出现时间冲突。
  2. 并发预约场景缺少资源锁,高并发下同一厨师同一时段生成多笔订单,造成超约。
  3. 订单状态缺少状态机约束,代码中随意修改状态,出现已取消订单仍然标记档期占用的脏数据。
  4. 订单取消、支付超时后,档期资源没有自动释放,厨师时段长期被占用,无法继续接单。
  5. 档期和订单数据不同步,厨师修改可接单时间,已经预约的历史订单没有做保护,引发履约纠纷。

二、整体技术架构设计

2.1 技术栈选型

后端采用Java SpringBoot + MyBatis Plus,数据库MySQL8.0,Redis用于档期资源锁与缓存可用档期,配合定时任务处理超时订单。
核心能力:厨师档期维护、时段资源锁定、订单状态机流转、超时订单自动处理、档期资源回收。

2.2 档期模型设计

上门做饭服务不能简单按天管理,需要拆分为日期+时段组合作为最小资源单元。
时段示例:午市(10:00-14:00)、晚市(16:00-21:00)。
厨师可以自主设置每周哪些时段可接单,也可以单独屏蔽某一天。当用户预约成功后,该【厨师ID+日期+时段】资源标记为占用,直到订单完成或取消后释放。

2.3 订单状态机设计

订单状态流转严格限制,禁止随意跳转:

  1. 待支付:用户提交预约,档期临时锁定,限时15分钟完成支付;超时自动取消并释放档期。
  2. 待接单:用户支付成功,等待厨师确认接单。
  3. 服务中:厨师确认接单,上门服务进行阶段。
  4. 已完成:服务结束,订单正常闭环。
  5. 已取消:用户主动取消 / 支付超时取消,档期资源释放。
  6. 已退款:履约前或履约中发起退款,退款完成,档期释放。

合法流转规则:待支付可以流转到待接单、已取消;待接单可以流转到服务中、已退款;服务中可以流转到已完成、已退款;已完成、已取消、已退款为终态,不可变更。

三、核心模块设计

3.1 档期管理模块

档期分为两层:厨师基础可接单模板、当日档期占用记录。

  1. 基础模板:厨师设置每周几、哪些时段可以接单,作为默认规则。
  2. 当日档期:系统根据基础模板生成每日可用档期;用户预约后,生成占用记录。厨师可以手动关闭某一天某个时段,临时暂停接单。
    查询逻辑:用户查询厨师档期时,优先读取Redis缓存,返回可用时段,减少数据库压力。

3.2 订单流转模块

  1. 预约下单:校验档期是否可用,通过Redis分布式锁锁定时段资源,生成待支付订单。
  2. 支付回调:支付成功后,变更订单状态为待接单,推送消息通知厨师。
  3. 厨师接单:确认接单,订单流转为服务中。
  4. 履约完成:厨师标记服务完成,订单变为已完成。
  5. 取消/超时:触发档期释放逻辑,解除资源占用。

四、Java核心代码实现

4.1 订单状态枚举(状态机)

/** * 上门做饭小程序订单状态枚举 */ public enum CookOrderStatusEnum { WAIT_PAY(1, "待支付"), WAIT_ACCEPT(2, "待接单"), SERVING(3, "服务中"), FINISHED(4, "已完成"), CANCEL(5, "已取消"), REFUND(6, "已退款"); private final Integer code; private final String desc; CookOrderStatusEnum(Integer code, String desc) { this.code = code; this.desc = desc; } /** * 校验状态是否允许流转 */ public static boolean canTransfer(Integer oldStatus, Integer newStatus) { if (oldStatus.equals(WAIT_PAY.getCode())) { return newStatus.equals(WAIT_ACCEPT.getCode()) || newStatus.equals(CANCEL.getCode()); } if (oldStatus.equals(WAIT_ACCEPT.getCode())) { return newStatus.equals(SERVING.getCode()) || newStatus.equals(REFUND.getCode()); } if (oldStatus.equals(SERVING.getCode())) { return newStatus.equals(FINISHED.getCode()) || newStatus.equals(REFUND.getCode()); } // 终态不能变更 return false; } public Integer getCode() { return code; } }

4.2 档期锁定与释放服务

@Service @Slf4j public class CookScheduleService { @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String SCHEDULE_LOCK_KEY = "cook:schedule:lock:%s:%s:%s"; /** * 锁定厨师档期资源 * @param cookId 厨师ID * @param bookDate 预约日期 yyyy-MM-dd * @param timeSlot 时段标识 * @return 锁定结果 */ public Result<Boolean> lockSchedule(Long cookId, String bookDate, String timeSlot) { String key = String.format(SCHEDULE_LOCK_KEY, cookId, bookDate, timeSlot); // 锁定有效期24小时,兜底防止死锁 Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "locked", 24, TimeUnit.HOURS); if (!success) { return Result.error("该时段已被预约,请更换日期或时段"); } log.info("档期锁定成功 cookId:{},date:{},slot:{}", cookId, bookDate, timeSlot); return Result.success(true); } /** * 释放档期资源 */ public void unLockSchedule(Long cookId, String bookDate, String timeSlot) { String key = String.format(SCHEDULE_LOCK_KEY, cookId, bookDate, timeSlot); redisTemplate.delete(key); log.info("档期资源释放 cookId:{},date:{},slot:{}", cookId, bookDate, timeSlot); } }

4.3 超时订单定时任务,自动取消释放档期

@Component @EnableScheduling @Slf4j public class OrderTimeoutTask { @Autowired private CookOrderMapper orderMapper; @Autowired private CookScheduleService scheduleService; /** * 每分钟扫描待支付超时订单 */ @Scheduled(cron = "0 * * * * ?") public void handleTimeoutOrder() { List<CookOrder> timeoutOrders = orderMapper.selectTimeoutWaitPayOrder(); if (CollectionUtils.isEmpty(timeoutOrders)) { return; } int count = 0; for (CookOrder order : timeoutOrders) { // 状态机校验,变更为已取消 if(!CookOrderStatusEnum.canTransfer(order.getStatus(), CookOrderStatusEnum.CANCEL.getCode())){ continue; } order.setStatus(CookOrderStatusEnum.CANCEL.getCode()); orderMapper.updateById(order); // 释放档期 scheduleService.unLockSchedule(order.getCookId(), order.getBookDate(), order.getTimeSlot()); count++; } log.info("超时订单处理完成,共取消{}笔订单", count); } }

五、数据库表设计

5.1 厨师档期配置表 cook_schedule_config

核心字段:id、cook_id、week_day、time_slot、is_enable、create_time
作用:存储厨师每周可接单时段模板。

5.2 厨师档期占用表 cook_schedule_occupy

核心字段:id、cook_id、book_date、time_slot、order_id、status
作用:记录每日时段占用情况,关联订单ID,作为数据库层面的档期记录。

5.3 预约订单表 cook_order

核心字段:id、order_no、user_id、cook_id、book_date、time_slot、address、total_price、status、pay_expire_time、create_time
作用:保存用户预约订单,status绑定状态机,pay_expire_time用于超时任务判断。

六、开发优化与避坑

  1. 缓存与数据库双校验:Redis锁做并发拦截,数据库作为最终一致性兜底,防止缓存宕机导致超约。
  2. 历史预约保护:厨师修改档期模板,不能修改已经存在有效订单的时段,避免破坏已预约订单。
  3. 状态变更统一收口:所有订单状态修改,必须调用状态机校验方法,禁止直接update修改status字段。
  4. 幂等设计:支付回调、订单完成回调都要增加幂等key,防止重复调用导致状态多次变更。
  5. 消息通知联动:订单状态变更后,通过小程序消息推送用户和厨师,及时同步预约、接单、取消信息。

七、总结

上门做饭小程序的核心难点不在于前端页面展示,而是档期资源的管理和订单全流程状态流转。档期是厨师的核心服务资源,使用【厨师+日期+时段】作为最小资源单元,搭配Redis分布式锁解决并发预约冲突;通过状态机约束订单流转,保证订单与档期数据联动。订单一旦取消或者超时,必须自动释放档期资源,最大化厨师接单能力。
该方案可以直接应用于上门做饭小程序后端开发,还可以扩展菜品套餐管理、订单分佣、评价体系、节日溢价等功能,适配同城私厨上门服务的商业化需求。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询