day-04
25-3-20
新增套餐
需求分析&设计
业务规则
- 套餐名称唯一
- 套餐必须属于某个分类
- 套餐必须包含菜品
- 名称、分类、价格、图片为必填项
- 添加菜品窗口需要根据分类类型来展示菜品
- 新增的套餐默认为停售状态
接口设计(共涉及到4个接口):
- 根据类型查询分类(已完成)
- 根据分类id查询菜品
- 图片上传(已完成)
- 新增套餐
根据菜品分类id查询菜品
新增套餐
代码开发
1. 根据菜品分类查询菜品
controller
/***根据分类id查询菜品** @return*///TODO 新增套餐时 搜索框里输入菜品名称进行搜索,前端好像没有写接口,日后再议@GetMapping("/list")@ApiOperation("根据分类id查询菜品")public Result<List<Dish>> list(Long categoryId){log.info("分类id查询菜品: id={}",categoryId);List<Dish> list = dishService.list(categoryId);return Result.success(list);}
service
//接口/*** 根据分类id查询菜品** @param categoryId* @return*/List<Dish> list(Long categoryId);//impl/*** 根据分类id查询菜品** @param categoryId* @return*/public List<Dish> list(Long categoryId) {Dish dish = Dish.builder().categoryId(categoryId).status(StatusConstant.ENABLE).build();return dishMapper.list(dish);}
mapper
/*** 动态条件查询菜品** @param dish* @return*/List<Dish> list(Dish dish);
dishMapper.xml
<!--动态条件查询菜品--><select id="list" resultType="com.sky.entity.Dish">select * from dish<where><if test="name != null">and name like concar('%',#{name},'%')</if><if test="categoryId !=null ">and category_id = #{categoryId}</if><if test="status !=null ">and status = #{status}</if></where>order by create_time desc</select>
2.新增套餐
controller
//SetmealController/*** 新增套餐及其对应菜品关系数据** @param setmealDTO* @return*/@PostMapping@ApiOperation("新增套餐")public Result save(@RequestBody SetmealDTO setmealDTO){log.info("新增套餐:{}",setmealDTO);setmealService.saveWithDish(setmealDTO);return Result.success();}
service
//接口void saveWithDish(SetmealDTO setmealDTO);//imple/*** 新增套餐及其对应菜品关系数据* 1.向套餐表中插入数据* 2.向套餐菜品关系表中插入多条数据** @param setmealDTO*/@Transactionalpublic void saveWithDish(SetmealDTO setmealDTO) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealDTO,setmeal);//向套餐表插入一条数据setmealMapper.insert(setmeal);//向套餐菜品关系表插入多条数据List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();//判断是否有菜品数据,并将套餐id填充if(setmealDishes != null && setmealDishes.size() > 0){setmealDishes.forEach(dish -> dish.setSetmealId(setmeal.getId()));//批量插入setmealDishMapper.insertBatch(setmealDishes);}}
mapper
//SetmealMapper/*** 新增套餐** @param setmeal*/@Insert("insert into setmeal (category_id, name, price, description, image, create_time, update_time, create_user, update_user)" +"values (#{categoryId},#{name},#{price},#{description},#{image},#{createTime},#{updateTime},#{createUser},#{updateUser})")@AutoFill(value = OperationType.INSERT)void insert(Setmeal setmeal);//SetmealDishMapper/*** 批量插入套餐菜品关系数据** @param setmealDishes*/void insertBatch(List<SetmealDish> setmealDishes);
xml
<!--批量插入套餐菜品关系数据--><insert id="insertBatch">insert into setmeal_dish (setmeal_id, dish_id,name,price,copies) values<foreach collection="list" item="sd" separator="," >(#{sd.setmealId}, #{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})</foreach></insert>
遇到的错误
动态sql参考:https://mybatis.net.cn/dynamic-sql.html
参考2:https://blog.csdn.net/weixin_44825912/article/details/130433302
新增套餐 批量插入套餐菜品关系数据 动态sql写错,还是不够熟练
//报错信息
### SQL: insert into setmeal_dish (setmeal_id, dish_id,name,price,copies) values ( ?, ?,?,?,?) , ?, ?,?,?,?) , ?, ?,?,?,?) , ?, ?,?,?,?) , ?, ?,?,?,?) , ?, ?,?,?,?) )动态sql写错。
继续学习动态sql<!--批量插入套餐菜品关系数据--><insert id="insertBatch">insert into setmeal_dish (setmeal_id, dish_id,name,price,copies) values<foreach collection="list" item="sd" open="(" separator="," close=")">#{sd.setmealId}, #{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})</foreach><!--修正后--><insert id="insertBatch">insert into setmeal_dish (setmeal_id, dish_id,name,price,copies) values<foreach collection="list" item="sd" separator="," >(#{sd.setmealId}, #{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})</foreach></insert>
功能测试&提交
功能一:根据菜品分类查询菜品完成
功能二:新增套餐
测试成功提交
套餐分页查询
需求分析&设计
业务规则:
- 根据套餐名称查询套餐
- 新增套餐 (已完成)
- 分页查询菜单
- 需要关联分类表展示套餐分类id对应的分类名
- 分页按照创建时间倒序排列
接口设计:
代码开发
controller
//SetmealController/*** 套餐分页查询** @param setmealPageQueryDTO* @return*/@GetMapping("/page")@ApiOperation("套餐分页查询")public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO){log.info("套餐分页查询信息:{}",setmealPageQueryDTO);PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);return Result.success(pageResult);}
service
//接口
PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);//impl/*** 套餐分页查询** @param setmealPageQueryDTO* @return*/public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {PageHelper.startPage(setmealPageQueryDTO.getPage(),setmealPageQueryDTO.getPageSize());Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);return new PageResult(page.getTotal(),page.getResult());}
mapper
/*** 分页查询套餐** @param setmealPageQueryDTO* @return*/Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
xml
<!--分页查询--><select id="pageQuery" resultType="com.sky.vo.SetmealVO">select s.*,c.name as categoryName from setmeal s left outer 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 status!=''">and s.status=#{status}</if></where>order by s.create_time desc</select>
功能测试&提交
测试成功&提交
删除套餐
需求分析&设计
业务规则:
- 要求实现批量删除
- 只有停售的套餐才可以被删除
- 删除套餐要同步批量删除菜品套餐关系表中的对应数据
接口设计:

代码开发
补充:Objects.equals(a,b)的作用与原理
1.作用:
-
用于安全的比较两个对象是否相等,避免
NullpointerException
-
源码
controller
/*** 批量删除套餐** @param ids* @return*/@DeleteMapping@ApiOperation("批量删除套餐")public Result delete(@RequestParam List<Long> ids){log.info("删除套餐:{}", ids);setmealService.deleteBatch(ids);return Result.success();}
service
//接口/*** 根据id批量删除套餐** @param ids*/void deleteBatch(List<Long> ids);//impl/*** 批量删除套餐** @param ids*/@Override@Transactionalpublic void deleteBatch(List<Long> ids) {//1.判断当前套餐是否在启售中for (Long id : ids){//根据套餐id查询套餐信息Setmeal setmeal = setmealMapper.getByid(id);//判断状态不为空,并且等于1 为启售中 启售中菜品不能删除if (Objects.equals(setmeal.getStatus(), StatusConstant.ENABLE)) {throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);}}//2.删除套餐表中对应的数据setmealMapper.deleteByIds(ids);//3.删除套餐菜品关系表中对应数据setmealDishMapper.deleteBySetmealIds(ids);}
mapper
//setmealMapper/*** 根据套餐id查询套餐信息** @param id* @return*/@Select("select * from setmeal where id = #{id}")Setmeal getByid(Long id);/*** 删除套餐表中对应数据** @param ids*/void deleteByIds(List<Long> ids);//setmealDishMapper/*** 根据套餐id批量删除套餐菜品关系数据** @param setmealIds*/void deleteBySetmealIds(List<Long> setmealIds);
sr.xml
<!--setmealMapepr.xml--><!--删除套餐表中对应数据--><delete id="deleteByIds">delete from setmeal where id in<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach></delete><!--setmealDishMapper-->
<!--根据套餐id批量删除套餐菜品关系数据--><delete id="deleteBySetmealIds">delete from setmeal_dish where setmeal_id in<foreach collection="setmealIds" item="setmealId" open="(" separator="," close=")">#{setmealId}</foreach></delete>
功能测试&提交
测试成功
修改套餐
需求分析&设计
业务规则:
- 套餐信息回显,根据套餐id查询套餐
- 接受前端DTO对象并修改

接口设计:


遇到错误错误
回显数据时不能回显套餐关联的菜品信息, 猜测可能是新增套餐时没有新增套餐和菜品关联关系的原因
setmeal_dish
果然是因为前面新增套餐时有bug,没有将套餐和菜品之间的关联关系插入
,现在回显还是有问题,但属于回显的问题---->查询,OK
果不其然,因为前面以为是回显查询sql出了问题,所有修改了sql,后面忘记了,现在修改回去就好了
代码开发
修改业务 & 回显数据:
SetmealController
/*** 回显套餐数据** @param id* @return*/@GetMapping("/{id}")@ApiOperation("根据id查询套餐数据")public Result<SetmealVO> getById(@PathVariable Long id){log.info("根据id查询套餐数据:id={}", id);SetmealVO setmealVO = setmealService.getByIdWithDish(id);return Result.success(setmealVO);} @PutMapping@ApiOperation("修改套餐")public Result update(@RequestBody SetmealDTO setmealDTO){log.info("修改套餐:{}",setmealDTO);setmealService.update(setmealDTO);return Result.success();}
SetmealSerivce
/*** 根据id查询套餐和菜品的关联关系** @param id* @return*/SetmealVO getByIdWithDish(Long id); /*** 根据id查询套餐和套餐菜品关系** @param id* @return*/public SetmealVO getByIdWithDish(Long id){//根据id查询套餐基本信息Setmeal setmeal = setmealMapper.getById(id);SetmealVO setmealVO = new SetmealVO();//根据id查询套餐和菜品的关联关系List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id);//将基本信息拷贝到VO中,给VO设置套餐和菜品的关联关系BeanUtils.copyProperties(setmeal,setmealVO);setmealVO.setSetmealDishes(setmealDishes);return setmealVO;}/*** 修改套餐** @param setmealDTO*/void update(SetmealDTO setmealDTO);//impl/*** 修改套餐** @param setmealDTO*/@Transactionalpublic void update(SetmealDTO setmealDTO) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealDTO, setmeal);//1、修改套餐表,执行updatesetmealMapper.update(setmeal);//套餐idLong setmealId = setmealDTO.getId();//2、删除套餐和菜品的关联关系,操作setmeal_dish表,执行deletesetmealDishMapper.deleteBySetmealId(setmealId);List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();setmealDishes.forEach(setmealDish -> {setmealDish.setSetmealId(setmealId);});//3、重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行insertsetmealDishMapper.insertBatch(setmealDishes);}
mapper
//setmealDishMapper/*** 根据id查询套餐和菜品的关联关系** @param setmealId* @return*/@Select("select * from setmeal_dish where setmeal_id = #{setmealId}")List<SetmealDish> getBySetmealId(Long setmealId);/*** 删除套餐关联id** @param id*/@Delete("delete from setmeal_dish where setmeal_id = #{id}")void deleteBySetmealId(Long id);//setmealMapper/***根据套餐id查询套餐信息** @param id* @return*/@Select("select * from setmeal where id = #{id}")Setmeal getById(Long id);/*** 修改套餐** @param setmeal*/@AutoFill(OperationType.UPDATE)void update(Setmeal setmeal);<!--动态 xml--><update id="update">update setmeal<set><if test="categoryId!=null">category_id=#{categoryId},</if><if test="name!=null and name!=''">name=#{name},</if><if test="price!=null">price=#{price},</if><if test="image!=null and image!=''">image=#{image},</if><if test="description!=null and description!=''">description=#{description},</if><if test="updateTime!=null">update_time=#{updateTime},</if><if test="updateUser!=null and updateUser!=''">update_user=#{updateUser},</if><if test="status!=null">status=#{status},</if></set>where id=#{id}</update>
功能测试&提交
测试成功& 提交
启售停售套餐
需求分析&设计
- 菜品停售会关联套餐停售
- 而套餐可以随意停售,不会影响单独菜品
所以我应该先写套餐停售
- 可以对状态为起售的套餐进行停售操作,可以对状态为停售的套餐进行起售操作
- 起售的套餐可以展示在用户端,停售的套餐不能展示在用户端
- 起售套餐时,如果套餐内包含停售的菜品,则不能起售
接口设计:

代码开发
controller
/*** 套餐启用 & 禁用** @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("套餐启用禁用")public Result status(@PathVariable Integer status,Long id){log.info("修改套餐为{}状态",status == StatusConstant.ENABLE ? "启售" : "停售");setmealService.startOrStop(status,id);return Result.success();}
service
/*** 套餐启售停售** @param status* @param id*/void startOrStop(Integer status,Long id);/*** 套餐启售停售** @param status* @param id*/@Overridepublic void startOrStop(Integer status, Long id) {//起售套餐时,判断套餐内是否有停售菜品,有停售菜品提示"套餐内包含未启售菜品,无法启售"//如果前端传过来要修改的状态为启售,那么我要继续检查该套餐里的菜品是否含有禁售菜品,如果有,抛出异常 +++if(status == StatusConstant.ENABLE){//select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = ?//查询该套餐包含的所有菜品 ,然后检查菜品状态是否为禁售List<Dish> dishList = dishMapper.getBySetmealId(id);if(dishList != null && dishList.size() > 0){dishList.forEach(dish -> {if(StatusConstant.DISABLE == dish.getStatus()){throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);}});}}//+++ 如果没有 就修改状态Setmeal setmeal = Setmeal.builder().id(id).status(status).build();setmealMapper.update(setmeal);}
mapper
//DishMapper/***根据id查询菜品信息** @param id* @return*/@Select("select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = #{id}")List<Dish> getBySetmealId(Long id);//setmealMapper/*** 修改套餐** @param setmeal*/@AutoFill(OperationType.UPDATE)void update(Setmeal setmeal);//服用之前的修改套餐SetmealMapper的update即可<!--动态 xml--><update id="update">update setmeal<set><if test="categoryId!=null">category_id=#{categoryId},</if><if test="name!=null and name!=''">name=#{name},</if><if test="price!=null">price=#{price},</if><if test="image!=null and image!=''">image=#{image},</if><if test="description!=null and description!=''">description=#{description},</if><if test="updateTime!=null">update_time=#{updateTime},</if><if test="updateUser!=null and updateUser!=''">update_user=#{updateUser},</if><if test="status!=null">status=#{status},</if></set>where id=#{id}</update>
功能测试&提交
补充一个菜品停售
- 因为写了套餐停售才能写菜品停售
controller
/*** 启用或禁用菜品* @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("启用或禁用菜品")public Result status(@PathVariable Integer status,Long id){dishService.startOrStop(status,id);return Result.success();}
service
/*** 菜品启售 禁售** @param status* @param id*/void startOrStop(Integer status, Long id); /*** 启用或禁用菜品** @param status* @param id*/@Overridepublic void startOrStop(Integer status, Long id) {Dish dish = Dish.builder().status(status).id(id).build();dishMapper.update(dish);}
mapper
/*** 根据id更新菜品** @param dish*/@AutoFill(OperationType.UPDATE)void update(Dish dish);<!--根据菜品id 动态修改菜品--><update id="update">update dish<set><if test="name!=null and name!=''">name=#{name},</if><if test="categoryId!=null">category_id=#{categoryId},</if><if test="price!=null">price=#{price},</if><if test="image!=null and image!=''">image=#{image},</if><if test="description!=null and description!=''">description=#{description},</if><if test="updateTime!=null">update_time=#{updateTime},</if><if test="updateUser!=null and updateUser!=''">update_user=#{updateUser},</if><if test="status!=null">status=#{status},</if></set>where id=#{id}</update>