苍穹外卖-day04

5 阅读5分钟

Day04 学习文档:项目实战——套餐管理(管理端)

对照原型 + 接口 + 表结构 + 业务规则,把套餐管理完整跑通


1) 你今天要完成的功能清单(做到这些就算 Day04 过关)

管理端「套餐管理」一般包含(day01 里也明确把“套餐管理”作为管理端核心模块之一):

  1. 套餐分页查询(列表页)
  2. 新增套餐(包含“套餐基本信息 + 套餐包含哪些菜品/份数”)
  3. 查询套餐详情(用于回显编辑页)
  4. 修改套餐
  5. 批量删除套餐
  6. 起售/停售套餐

你可以把它理解为:维护两张表的数据一致性:setmeal(套餐主表) + setmeal_dish(套餐-菜品关系表)。


2) 先把数据模型想清楚

day01 的库表清单里就有:setmealsetmeal_dish

  • setmeal:套餐本身(名字、分类、价格、图片、描述、状态…)
  • setmeal_dish:套餐包含哪些菜品、每个菜品几份(copies),属于典型的“一对多”(一个套餐对应多条明细)

你在 day03 删除菜品时已经遇到过“被套餐关联不能删”的规则,本质就是检查 setmeal_dish 这张关系表 。Day04 反过来:你要维护/清理这张关系表的数据。


3) 接口清单

  • POST /admin/setmeal:新增套餐(Body:SetmealDTO
  • GET /admin/setmeal/page:分页查询(Query:name/categoryId/status/page/pageSize…)
  • GET /admin/setmeal/{id}:查询套餐详情(用于回显)
  • PUT /admin/setmeal:修改套餐(Body:SetmealDTO
  • DELETE /admin/setmeal?ids=1,2,3:批量删除
  • POST /admin/setmeal/status/{status}?id=xx:起售/停售

4) 项目里你该看哪些代码(按这个顺序看,最省时间)

  • Controller:com.sky.controller.admin.SetmealController
  • Service:com.sky.service.SetmealServicecom.sky.service.impl.SetmealServiceImpl
  • Mapper:com.sky.mapper.SetmealMapperSetmealDishMapper、必要时 DishMapper
  • XML:resources/mapper/SetmealMapper.xmlSetmealDishMapper.xml

建议你学习方式是:先从 Controller 看“有哪些接口” → 再进 Service 看“业务规则” → 最后看 Mapper/XML 明白“SQL 怎么落库”。


5) 关键业务怎么写(每个功能你都要能讲清楚“数据怎么动”)

A. 新增套餐(最核心:一次写两张表)

目标:插入 setmeal 一条 + 插入 setmeal_dish 多条

思路:

  1. SetmealDTO 拿到套餐基本信息 + setmealDishes 列表
  2. 先 insert setmeal,拿到生成的 setmealId
  3. 遍历 setmealDishes,给每条设置 setmealId
  4. 批量 insert 到 setmeal_dish

你要重点理解:为什么必须先插 setmeal?
因为 setmeal_dish.setmeal_id 需要主表 id,主表 id 是自增生成的。

✅ 自检点:

  • 只新增主表、不新增关系表:前端回显/用户端展示必坏
  • 新增关系表但没设置 setmealId:会出现“明细全是 null 外键”或直接插入失败

B. 套餐分页查询(列表页)

目标:按条件分页查 setmeal,一般还要带出分类名(categoryName)

思路:

  • 用 PageHelper + mapper XML 里 join category 的写法(类似 day03 菜品分页 join 分类)
  • 返回 PageResult(total, records)

✅ 自检点:

  • pagepageSize 是否生效(SQL 有没有 limit)
  • 条件过滤:name 模糊、categoryId 精确、status 精确

C. 查询套餐详情(编辑页回显)

目标:查出:

  • 套餐基本信息(setmeal)
  • 套餐包含的菜品明细(setmeal_dish 列表)

思路:

  1. setmealMapper.getById(id) 拿主表
  2. setmealDishMapper.getBySetmealId(id) 拿明细
  3. 组装成 SetmealVO 返回(VO 里包含 setmealDishes

✅ 自检点:

  • 回显页能否显示“已选菜品 + 份数”
  • 如果套餐没有菜品(理论不应出现),接口也要稳,不要 NPE

D. 修改套餐(本质:先清空旧明细,再插入新明细)

目标:更新 setmeal 一条 + 重建 setmeal_dish 多条

推荐做法(最稳):

  1. update setmeal
  2. delete setmeal_dish where setmeal_id = ?
  3. 遍历新 setmealDishes set 外键
  4. batch insert 新明细

✅ 自检点:

  • “删旧明细”必须做,否则会出现“旧菜品还在 + 新菜品又加进来”的脏数据
  • 最好加事务:update 成功但明细插入失败会导致数据不一致(真实项目里这是事故点)

E. 批量删除套餐(业务规则通常更严格)

常见规则:

  1. 起售中的套餐不能删除(避免用户侧还能买到)
  2. 删除套餐时必须删除关系表数据(否则孤儿数据)
  3. 批量删除要么全成功要么全失败(建议事务)

✅ 自检点:

  • 先查 status 再删,别直接 delete
  • 删除顺序:通常先删关系表再删主表,或主表删完再删关系表都行,但要保证最终一致

F. 起售/停售套餐(容易考:联动校验)

常见规则:

  • 起售套餐前,要确保“套餐里所有菜品都在售”(否则套餐上架但里面菜品下架,逻辑矛盾)

实现上通常会:

  1. 根据 setmealId 查出其菜品列表(通过 setmeal_dish 关联 dish)
  2. 若存在停售菜品:抛业务异常
  3. 校验通过:更新 setmeal.status

✅ 自检点:

  • 你能用断点看到:为什么能查到这个套餐包含哪些菜品?
  • 报错信息要“业务可读”(不是 SQL 异常)

6) 测试路线

你 day03 已经用过接口文档测试:http://localhost:8080/doc.html,并且提醒过 token 失效要重新登录获取 。day04 继续沿用这个节奏:

建议测试顺序:

  1. 分页查询:先确保列表能出数据
  2. 新增套餐:新增完回到分页确认能看到
  3. 查询详情:拿新增的 id 回显确认 setmealDishes 正确
  4. 修改套餐:改套餐里的菜品组合,回显确认旧明细已替换
  5. 起售/停售:验证规则是否生效(尤其“含停售菜品不能起售”)
  6. 删除套餐:先尝试删起售套餐(应失败),再停售后删除(应成功)

联调测试(可选但推荐):

  • 启动 nginx → 进“套餐管理”页面 → 跟着 UI 点一遍(这一步能暴露很多字段映射问题)

7) 常见坑

  1. 事务没加导致数据不一致
    修改/删除这类“一次动多张表”的操作,推荐都加 @Transactional(至少:新增、修改、删除)
  2. MyBatis 单参数命名坑(特别常见)
    Mapper 方法如果只有一个参数但 XML 用了别的名字,可能出现:Parameter not found
    解决方式:要么 XML 用 #{param1} / #{id} 对齐,要么在 Mapper 参数上加 @Param("xxx")
  3. BeanUtils.copyProperties 把不该覆盖的字段覆盖了
    修改时注意:是否需要保留 createTime/createUser 等(项目里一般用公共字段自动填充解决)
  4. 前端传参字段名对不上
    典型:categoryId、image、setmealDishes 这类字段,一旦名字不一致,后端就收不到