当前位置: 首页 > news >正文

苍穹外卖实战:套餐管理核心CRUD与状态流转的工程化实现

1. 套餐管理模块架构设计

在苍穹外卖系统中,套餐管理模块采用经典的三层架构设计,这种分层结构我在多个电商项目中验证过其稳定性。先来看整体架构:

  • Controller层:接收前端请求,处理参数校验,调用Service层方法
  • Service层:实现核心业务逻辑,处理事务控制
  • Mapper层:负责数据库操作,通过MyBatis实现

实际开发中我习惯先设计领域模型,套餐管理涉及两个核心实体:

// 套餐主表实体 public class Setmeal { private Long id; private String name; private Long categoryId; // 分类ID private BigDecimal price; private Integer status; // 状态 1起售 0停售 // 其他字段... } // 套餐菜品关系实体 public class SetmealDish { private Long id; private Long setmealId; // 套餐ID private Long dishId; // 菜品ID private Integer copies; // 份数 // 其他字段... }

数据库表设计遵循几个原则:

  1. 套餐表(setmeal)与菜品表(dish)通过中间表(setmeal_dish)建立多对多关系
  2. 套餐名称添加唯一索引确保唯一性
  3. 外键关联分类表(category)保证数据完整性

2. 新增套餐实现细节

新增套餐是典型的分布式事务场景,需要同时操作套餐主表和套餐菜品关系表。我在实际项目中遇到过因事务未处理导致的脏数据问题,这里分享解决方案:

@Transactional public void saveWithDish(SetmealDTO setmealDTO) { // 1. 保存套餐基本信息 Setmeal setmeal = new Setmeal(); BeanUtils.copyProperties(setmealDTO, setmeal); setmealMapper.insert(setmeal); // 使用@Options获取自增ID // 2. 保存套餐菜品关系 List<SetmealDish> dishes = setmealDTO.getSetmealDishes(); dishes.forEach(dish -> dish.setSetmealId(setmeal.getId())); setmealDishMapper.insertBatch(dishes); }

几个关键点需要注意:

  1. 事务控制:使用@Transactional注解保证原子性
  2. 性能优化:批量插入代替循环单条插入
  3. 业务校验
// 校验套餐名称唯一性 LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Setmeal::getName, setmealDTO.getName()); if(setmealMapper.selectCount(queryWrapper) > 0){ throw new BusinessException("套餐名称已存在"); } // 校验关联菜品状态 setmealDTO.getSetmealDishes().forEach(dish -> { Dish dbDish = dishMapper.selectById(dish.getDishId()); if(dbDish == null || dbDish.getStatus() == 0){ throw new BusinessException("包含无效或停售菜品"); } });

3. 套餐分页查询优化

分页查询是高频操作,经过多次性能测试后我总结出以下优化方案:

public PageResult pageQuery(SetmealPageQueryDTO dto) { // 使用PageHelper实现物理分页 PageHelper.startPage(dto.getPage(), dto.getPageSize()); // 动态SQL查询 Page<SetmealVO> page = setmealMapper.pageQuery(dto); return new PageResult(page.getTotal(), page.getResult()); }

对应的XML映射文件:

<select id="pageQuery" resultType="com.sky.vo.SetmealVO"> select s.*,c.name as categoryName from setmeal s left join category c on s.category_id = c.id <where> <if test="name != null and name != ''"> and s.name like concat('%',#{name},'%') </if> <if test="categoryId != null"> and s.category_id = #{categoryId} </if> <if test="status != null"> and s.status = #{status} </if> </where> order by s.create_time desc </select>

实际开发中容易踩的坑:

  1. N+1查询问题:避免在循环中查询关联数据
  2. 分页插件冲突:MyBatis-Plus与PageHelper不要混用
  3. 模糊查询优化:大数据量时考虑使用全文索引

4. 套餐状态流转设计

套餐的起售/停售是典型的状态模式应用场景,我通过状态机实现状态流转控制:

public void startOrStop(Integer status, Long id) { // 状态转换校验 Setmeal setmeal = setmealMapper.selectById(id); if(setmeal == null){ throw new BusinessException("套餐不存在"); } // 启售时校验菜品状态 if(status == StatusConstant.ENABLE){ List<Dish> dishes = dishMapper.getBySetmealId(id); if(dishes.stream().anyMatch(d -> d.getStatus() == StatusConstant.DISABLE)){ throw new BusinessException("套餐内包含停售菜品"); } } // 更新状态 Setmeal update = new Setmeal(); update.setId(id); update.setStatus(status); setmealMapper.update(update); // 清理缓存(如有) cleanCache(setmeal.getCategoryId()); }

状态流转的注意事项:

  1. 并发控制:使用乐观锁防止状态覆盖
  2. 缓存一致性:状态变更后及时更新缓存
  3. 关联影响:套餐状态变更可能影响订单业务

5. 事务与异常处理实践

在套餐删除操作中,我采用声明式事务保证数据一致性:

@Transactional public void deleteBatch(List<Long> ids) { // 校验套餐状态 ids.forEach(id -> { Setmeal setmeal = setmealMapper.selectById(id); if(setmeal.getStatus() == StatusConstant.ENABLE){ throw new BusinessException("起售套餐不可删除"); } }); // 删除套餐记录 setmealMapper.deleteByIds(ids); // 删除关联菜品关系 setmealDishMapper.deleteBySetmealIds(ids); }

异常处理的最佳实践:

  1. 自定义异常体系
public class BusinessException extends RuntimeException { private Integer code; public BusinessException(String message) { super(message); this.code = 500; } }
  1. 全局异常处理器
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException ex){ return Result.error(ex.getMessage()); } }
  1. 事务回滚策略:默认对RuntimeException回滚

6. 工程化实践建议

经过多个项目迭代,我总结出以下工程化实践:

  1. 接口文档管理
  • 使用Swagger生成实时API文档
  • 配合YAPI进行接口Mock
  1. 代码规范
  • Controller方法不超过50行
  • Service方法保持单一职责
  • 复杂SQL写在XML中
  1. 性能监控
// 使用AOP监控方法耗时 @Around("execution(* com.sky.service.impl.*.*(..))") public Object monitorTime(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); log.info("方法执行耗时: {}ms", System.currentTimeMillis()-start); return result; }
  1. 缓存策略
  • 套餐列表使用Redis缓存
  • 采用Cache-Aside模式
  • 设置合理的过期时间

在实际开发中,我发现套餐管理模块最常出现的问题是事务边界划分不清。建议按照以下原则划分事务:

  • 跨表操作需要事务
  • 查询方法不加事务
  • 批量操作使用独立事务

最后分享一个调试技巧:在开发套餐状态流转时,可以使用Spring的@PostConstruct模拟状态变化:

@Component public class SetmealStatusDebug { @Autowired private SetmealService setmealService; @PostConstruct public void init(){ // 模拟启售操作 setmealService.startOrStop(1, 1L); } }
http://www.jsqmd.com/news/1186890/

相关文章:

  • 重塑供应链!2026武汉数字孪生产业展览会亮相,打造工业未来新基石
  • 亨得利官方钟表服务中心|官方热线与门店地址权威信息公告(2026年7月更新) - 亨得利官方
  • FGA自动战斗配置终极指南:从零开始掌握FGO智能辅助工具
  • 104个点位布局+北京16区全覆盖|2026本地黄金回收便民地图 - 奢侈品回收测评
  • 教培机构数字化转型全指南:报名管家小程序如何用“报名+核销”重塑教学运营四大高频场景 - 亲测好用工具
  • 社会规则的生命周期的庖丁解牛
  • 计算机网络-2021期末真题深度剖析与实战演练
  • OpenCore Legacy Patcher终极指南:3步让老Mac焕发新生命
  • 2026湖州黄金回收攻略:观金堂等3家实体店 - 观金堂黄金回收
  • Windows屏幕标注终极解决方案:ppInk免费开源工具完全指南
  • 智连万物,能动未来:2026武汉国际连接器线缆及线束加工展会开启工业新纪元
  • Unity编辑器扩展实战:基于IMGUI实现可拖拽排序的格子列表
  • 行测攻坚:排列组合与概率、容斥原理的实战场景拆解
  • 探索Earthdata Search高级功能:空间筛选、时间轴分析与数据导出技巧
  • 北京丰台区闲置黄金变现 2026 攻略,新手回收避雷要点,透明交易认准易奢福 - 肉松卷
  • Ubuntu下USB摄像头的检测、管理与高级应用工具指南
  • 深度解析.NET运行时增强:Harmony库的5大实战应用场景
  • 旋转夹爪供应商怎么选型?2026年高性能旋转夹爪厂家品牌盘点 - 品牌深度评测
  • bokeh-notebooks性能优化:加速大规模数据可视化的7个技巧
  • 鸣潮自动化终极指南:5分钟快速上手ok-ww后台挂机工具
  • AI编程在港口物流软件中的实践与挑战
  • 10分钟快速上手:ComfyUI-LTXVideo AI视频生成终极指南
  • 组策略笔记
  • AI 驱动的链上交易排序优化:MEV 感知的智能路由与批量打包策略
  • 武汉三新高级技工学校 2026 招生|官网入口、校区地址、招生热线、全部专业一览 - Luckyone王
  • 如何快速配置本地AI助手:面向隐私保护的完整指南
  • 从频谱泄露到精准分析:FFT信号加窗的原理、实践与窗函数选型指南
  • C++实现线性回归:从零构建机器学习基础,深入理解算法本质
  • OpenCV —— 特征点检测之 SIFT 算法原理与实战解析
  • 2026抚州黄金回收全攻略:福满多黄金回收(临川旗舰店)领衔5家实体门店 - 观金堂黄金回收