从产品经理视角看技术实现:拆解‘苍穹外卖’套餐管理的业务逻辑与接口设计
从产品经理视角看技术实现:拆解‘苍穹外卖’套餐管理的业务逻辑与接口设计
在数字化餐饮服务领域,套餐管理模块的设计直接影响运营效率和用户体验。作为连接商业策略与技术落地的关键环节,产品经理需要深入理解业务规则如何转化为系统约束,而开发者则需把握功能需求背后的商业意图。本文将以"苍穹外卖"的套餐管理为样本,揭示从产品原型到代码实现的完整思维链条。
1. 业务规则的技术映射
产品文档中"套餐名称唯一性"的约束,在技术实现上体现为多层次的防御性编程:
// SetmealService.java public void saveWithDish(SetmealDTO setmealDTO) { // 唯一性校验 Setmeal existing = setmealMapper.getByName(setmealDTO.getName()); if (existing != null) { throw new DuplicateKeyException("套餐名称已存在"); } // 后续处理逻辑... }关键校验点矩阵:
| 业务规则 | 技术实现方案 | 异常处理方式 |
|---|---|---|
| 套餐名称唯一 | 数据库唯一索引+服务层校验 | DuplicateKeyException |
| 必须关联有效分类 | 外键约束+分类状态检查 | InvalidCategoryException |
| 包含菜品数≥1 | Collection.size()校验 | EmptySetmealException |
| 价格必须为正数 | @Min注解校验 | ConstraintViolationException |
提示:在事务边界处理上,套餐创建涉及setmeal和setmeal_dish两张表的操作,必须使用@Transactional保证原子性
2. 状态管理的协同逻辑
套餐的起售/停售状态与关联菜品存在强耦合关系,这要求产品设计时考虑状态机模型:
stateDiagram-v2 [*] --> 停售 停售 --> 起售: 需所有菜品已启售 起售 --> 停售: 无条件实际代码通过组合查询实现状态校验:
// DishMapper.xml <select id="getBySetmealId" resultType="Dish"> SELECT d.* FROM dish d JOIN setmeal_dish sd ON d.id = sd.dish_id WHERE sd.setmeal_id = #{setmealId} AND d.status = 0 </select>状态变更的防御措施:
- 前端禁用停售菜品的勾选
- 后端缓存菜品状态快照
- 异步任务定期检查数据一致性
3. 复杂操作的事务设计
批量删除套餐的案例展示了分布式事务的简化方案:
// SetmealServiceImpl.java @Transactional public void deleteBatch(List<Long> ids) { // 第一阶段:校验 ids.forEach(id -> { Setmeal setmeal = setmealMapper.getById(id); if (setmeal.getStatus() == StatusConstant.ENABLE) { throw new BusinessException("起售套餐不可删除"); } }); // 第二阶段:执行 ids.forEach(id -> { setmealMapper.deleteById(id); // 主表删除 setmealDishMapper.deleteBySetmealId(id); // 关联表删除 }); }事务隔离要点:
- 采用REQUIRED传播级别确保操作在同一个事务中
- 先查询后删除模式避免死锁
- 批量操作建议分片处理(如每100条一个批次)
4. 前后端协作的接口艺术
套餐分页查询的接口设计体现了前后端分离的最佳实践:
// SetmealController.java @GetMapping("/page") public Result<PageResult> pageQuery(SetmealPageQueryDTO dto) { // 构建查询条件 SetmealPageQueryVO vo = new SetmealPageQueryVO(); BeanUtils.copyProperties(dto, vo); // 执行分页查询 PageHelper.startPage(dto.getPage(), dto.getPageSize()); List<SetmealVO> records = setmealMapper.pageQuery(vo); return Result.success(new PageResult( ((Page)records).getTotal(), records )); }接口设计黄金法则:
- 入参使用DTO隔离领域模型
- 出参包装统一响应结构
- 分页参数与业务参数分离
- 字段命名遵循前端语义
在修改套餐的实现中,我们采用"查询-修改"双接口模式:
// 回显接口 @GetMapping("/{id}") public Result<SetmealVO> getDetail(@PathVariable Long id) { SetmealVO vo = setmealService.getDetailWithDishes(id); return Result.success(vo); } // 提交接口 @PutMapping public Result update(@Valid @RequestBody SetmealDTO dto) { setmealService.updateWithDishes(dto); return Result.success(); }这种设计虽然增加了接口数量,但显著降低了前端复杂度,符合RESTful资源操作理念。
