苍穹外卖-day04
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
*/
@Transactional
public 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
@Transactional
public 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
*/
@Transactional
public void update(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
//1、修改套餐表,执行update
setmealMapper.update(setmeal);
//套餐id
Long setmealId = setmealDTO.getId();
//2、删除套餐和菜品的关联关系,操作setmeal_dish表,执行delete
setmealDishMapper.deleteBySetmealId(setmealId);
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//3、重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行insert
setmealDishMapper.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
*/
@Override
public 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
*/
@Override
public 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>

浙公网安备 33010602011771号