苍穹外卖——项目实战:套餐管理模块的CRUD全流程解析
1. 套餐管理模块的业务规则解析开发外卖系统的套餐管理模块时首先要吃透业务规则。这就像做菜前要先了解食材特性一样重要。我遇到过不少开发者直接上手写代码结果发现业务逻辑不匹配又要返工的情况。下面这些关键业务规则建议你在开发前用马克笔写在白板上唯一性校验是基础中的基础。套餐名称必须全局唯一这就像餐厅里不能有两道同名菜品。实际开发中我推荐在数据库层和代码层做双重校验避免并发问题。具体实现时可以在setmeal表的name字段加唯一索引同时在Service层先做查询校验。必填项控制包括套餐名称、分类、价格、图片四个核心字段。前端可以做基础校验但后端必须进行二次验证。这里有个容易踩的坑价格字段要用BigDecimal类型用double会出现精度问题。我曾在项目中发现0.10.20.30000000000000004这种经典问题。菜品关联规则要求每个套餐至少包含一个菜品。这个校验要放在事务的最后一步确保套餐基础信息入库成功后再校验菜品列表。曾经有同事把校验放在最前面结果遇到套餐信息保存失败时用户却收到了菜品不能为空的误导提示。状态机设计方面新增套餐默认处于停售状态需要手动启售。这符合餐饮行业先准备后上架的流程。启售时要特别注意连锁反应如果套餐内包含停售的菜品整个套餐也不能启售。这个逻辑要放在Service层实现建议用Stream API过滤菜品状态boolean hasDisabledDish setmealDishes.stream() .anyMatch(dish - dish.getStatus() StatusConstant.DISABLE);2. 数据库设计与优化技巧数据库设计就像盖房子的地基我见过太多项目后期因为早期设计缺陷而推倒重来。套餐管理涉及setmeal和setmeal_dish两张核心表有些设计细节值得展开说说主键策略推荐使用数据库自增ID相比UUID等方案更符合餐饮业务特点。注意MyBatis要配置Options(useGeneratedKeys true)实现主键回填否则新增套餐后拿不到ID导致关联菜品失败。这个坑我踩过调试了两小时才发现问题。冗余字段在setmeal_dish表中存在菜品名称和单价。这属于典型的空间换时间策略避免每次查询都要联表查菜品表。但要注意数据一致性当菜品信息变更时记得用触发器或代码同步更新关联套餐。我有次忘了处理导致用户看到的套餐价格与实际不符。索引优化方面这几个字段必须建索引setmeal.name唯一索引setmeal.category_id普通索引setmeal_dish.setmeal_id外键索引setmeal_dish.dish_id外键索引联表查询时建议使用LEFT JOIN代替子查询。例如分页查询时要关联分类表获取分类名称SELECT s.*, c.name AS category_name FROM setmeal s LEFT JOIN category c ON s.category_id c.id WHERE s.status 1事务管理是另一个重点。新增套餐时要同时操作两张表必须用Transactional保证原子性。建议将事务隔离级别设为REPEATABLE_READ防止脏读。曾经有个线上bug就是因为事务配置不当导致套餐显示不全。3. 核心接口实现详解接口实现是业务逻辑的落脚点这里我结合自己趟过的坑详细解析几个关键接口。3.1 新增套餐接口这个接口要处理文件上传、数据校验、主表从表操作等多个步骤。建议拆分为以下子流程参数校验用Hibernate Validator做基础校验PostMapping public Result save(Valid RequestBody SetmealDTO setmealDTO) { // 业务校验 if (setmealDTO.getSetmealDishes() null || setmealDTO.getSetmealDishes().isEmpty()) { throw new BusinessException(套餐必须包含菜品); } }对象转换使用BeanUtils要注意深浅拷贝问题。我更喜欢用MapStruct性能更好Mapper(componentModel spring) public interface SetmealMapper { Setmeal toEntity(SetmealDTO dto); }主表操作特别注意主键回填配置Options(useGeneratedKeys true, keyProperty id) Insert(insert into setmeal(...) values(...)) void insert(Setmeal setmeal);从表操作批量插入要优化性能insert idinsertBatch INSERT INTO setmeal_dish VALUES foreach collectionlist itemitem separator, (#{item.setmealId}, #{item.dishId}, ...) /foreach /insert3.2 分页查询接口分页查询要注意性能优化。我推荐使用PageHelper配合自定义VOpublic PageResult pageQuery(SetmealPageQueryDTO dto) { PageHelper.startPage(dto.getPage(), dto.getPageSize()); PageSetmealVO page setmealMapper.pageQuery(dto); return new PageResult(page.getTotal(), page.getResult()); }VO对象要包含关联表字段Data public class SetmealVO { private Long id; private String name; private String categoryName; // 关联字段 private ListSetmealDish dishes; }3.3 批量删除接口删除操作要特别注意数据一致性检查套餐状态启售中的不能删除使用事务保证两张表同步删除批量操作要优化SQLTransactional public void deleteBatch(ListLong ids) { // 状态检查 if (setmealMapper.countEnabledByIds(ids) 0) { throw new BusinessException(存在启售中的套餐); } // 批量删除 setmealMapper.deleteBatch(ids); setmealDishMapper.deleteBySetmealIds(ids); }4. 前后端联调实战技巧前后端联调阶段最容易出现扯皮情况这里分享几个实用技巧Swagger配置要规范推荐这样写Operation(summary 修改套餐) Parameters({ Parameter(name id, description 套餐ID, required true), Parameter(name status, description 状态 1启售 0停售) }) PostMapping(/status/{status}) public Result updateStatus(PathVariable Integer status, Long id) { //... }参数校验要前后端统一规则。比如价格校验NotNull DecimalMin(value 0.01, message 价格不能小于0.01) private BigDecimal price;异常处理建议全局统一格式ExceptionHandler(BusinessException.class) public Result handleException(BusinessException ex) { log.error(业务异常, ex); return Result.error(ex.getMessage()); }联调技巧使用Postman先调试接口开启MyBatis日志检查SQL用Mock数据绕过复杂依赖善用Chrome开发者工具检查请求5. 常见问题排查指南开发过程中难免会遇到各种问题这里整理几个典型问题的解决方案问题一主键未回填现象套餐菜品关联表中的setmeal_id为null 解决方案检查Mapper是否配置Options(useGeneratedKeys true)确认数据库表主键是自增的检查MyBatis版本是否兼容问题二批量插入失败现象报错SQL语法错误 解决方案检查MySQL连接参数要加allowMultiQueriestrue确认批量SQL语法正确检查字段数量与值数量是否匹配问题三事务不生效现象部分操作失败未回滚 解决方案确认方法为public且被外部调用检查异常类型是否被捕获确认数据库引擎支持事务(InnoDB)问题四分页查询慢现象数据量大了查询变慢 解决方案确保分页字段有索引使用延迟关联优化SELECT * FROM setmeal WHERE id IN ( SELECT id FROM setmeal WHERE ... LIMIT 10000, 10 )6. 性能优化建议当套餐数据量上来后这些优化手段能显著提升性能缓存策略使用Redis缓存热门套餐采用多级缓存架构注意缓存与数据库一致性SQL优化避免SELECT *只查必要字段复杂查询走索引覆盖大数据量分页用游标方式异步处理菜品变更消息发MQ异步更新套餐冗余字段日志记录走异步线程池代码优化使用批量操作代替循环预编译SQL语句对象复用减少GC压力7. 安全防护措施餐饮系统涉及交易安全必须重视输入校验防XSS对用户输入转义处理防SQL注入用预编译语句文件上传限制类型和大小权限控制接口级权限校验数据权限过滤操作日志审计敏感数据价格等字段加密传输日志脱敏处理防爬虫频率限制8. 扩展功能思路基础功能上线后可以考虑这些扩展方向组合套餐允许用户自定义搭配时段套餐不同时段展示不同套餐智能推荐根据用户历史推荐套餐套餐分析销量统计和预测多规格支持套餐内菜品可选规格在实际项目中我遇到过需要支持套餐预售的场景这需要额外增加预售时间和库存字段。建议在设计初期就考虑这些可能的扩展点预留好字段和接口。