课程内容

  • 本日任务为作业 自主完成
  • 新增套餐
  • 删除套餐
  • 修改套餐
  • 起售停售套餐

其他要求

  • 根据产品原型进行需求分析 分析出业务规则
  • 设计接口
  • 梳理表之间的关系
  • 根据接口设计进行代码实现
  • 通过swagger和前后端联调进行功能测试

新增套餐

问题分析

  • 套餐必定包含几个菜品
  • 套餐名称唯一
  • 新增套餐默认为停售状态
  • 根据分页分类类型展示菜品

接口设计

  • 根据分类id查询菜品
  • 新增套餐

数据库设计

字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 套餐名称 唯一
category_id bigint 分类id 逻辑外键
price decimal(10,2) 套餐价格
image varchar(255) 图片路径
description varchar(255) 套餐描述
status int 售卖状态 1起售 0停售
create_time datetime 创建时间
update_time datetime 最后修改时间
create_user bigint 创建人id
update_user bigint 最后修改人id

setmeal_dish表为套餐菜品关系表,用于存储套餐和菜品的关联关系。具体表结构如下:

字段名 数据类型 说明 备注
id bigint 主键 自增
setmeal_id bigint 套餐id 逻辑外键
dish_id bigint 菜品id 逻辑外键
name varchar(32) 菜品名称 冗余字段
price decimal(10,2) 菜品单价 冗余字段
copies int 菜品份数

代码开发

  • 同时完成查询分类方法以及新增方法
  • 设计dto类
@Data
public class SetmealDTO implements Serializable {

    private Long id;

    //分类id
    private Long categoryId;

    //套餐名称
    private String name;

    //套餐价格
    private BigDecimal price;

    //状态 0:停用 1:启用
    private Integer status;

    //描述信息
    private String description;

    //图片
    private String image;

    //套餐菜品关系
    private List<SetmealDish> setmealDishes = new ArrayList<>();

}

Contreller层

  • 创建DishController处理菜品相关请求
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
public class DishController {
    
    @Autowired
    private DishService dishService;
    
    /**
     * 根据分类id查询菜品
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<Dish>> list(Long categoryId) {
        List<Dish> list = dishService.list(categoryId);
        return Result.success(list);
    }
}
  • 创建SetmealController处理套餐相关请求
```@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    /**
     * 新增套餐
     * @param setmealDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增套餐")
    public Result save(@RequestBody SetmealDTO setmealDTO) {
        setmealService.saveWithDish(setmealDTO);
        return Result.success();
    }
}

Server层

  • 菜品服务实现
```@Service
public class DishServiceImpl implements DishService {
    
    @Autowired
    private DishMapper dishMapper;
    
    /**
     * 根据分类id查询菜品
     * @param categoryId
     * @return
     */
    @Override
    public List<Dish> list(Long categoryId) {
        Dish dish = Dish.builder()
                .categoryId(categoryId)
                .status(StatusConstant.ENABLE)
                .build();
        return dishMapper.list(dish);
    }
}
  • 套餐服务实现
@Service
public class SetmealServiceImpl implements SetmealService {

    @Autowired
    private SetmealMapper setmealMapper;
    @Autowired
    private SetmealDishMapper setmealDishMapper;

    /**
     * 新增套餐
     * 需要保存套餐与菜品的关系
     */
    @Transactional
    @Override
    public void saveWithDish(SetmealDTO setmealDTO) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO, setmeal);

        //向套餐表插入数据
        setmealMapper.insert(setmeal);

        //获取生成的套餐id
        Long setmealId = setmeal.getId();

        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
        });

        //保存套餐和菜品的关联关系
        setmealDishMapper.insertBatch(setmealDishes);
    }
}

Mapper层

  • 菜品Mapper接口定义
public interface DishMapper {
    /**
     * 动态条件查询菜品
     * @param dish
     * @return
     */
    List<Dish> list(Dish dish);
}
  • 菜品Mapper XML实现
```<select id="list" resultType="Dish" parameterType="Dish">
    select * from dish
    <where>
        <if test="name != null">
            and name like concat('%',#{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>
  • 套餐Mapper接口定义
public interface SetmealMapper {
    /**
     * 新增套餐
     * @param setmeal
     */
    void insert(Setmeal setmeal);
}
  • 套餐Mapper XML实现

<insert id="insert" parameterType="Setmeal" useGeneratedKeys="true" keyProperty="id">
    insert into setmeal
    (category_id, name, price, status, description, image, create_time, update_time, create_user, update_user)
    values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime},
            #{createUser}, #{updateUser})
</insert>
  • 套餐菜品关系Mapper接口定义
public interface SetmealDishMapper {
    /**
     * 批量保存套餐和菜品的关联关系
     * @param setmealDishes
     */
    void insertBatch(List<SetmealDish> setmealDishes);
}
  • 套餐菜品关系Mapper XML实现
<insert id="insertBatch" parameterType="list">
    insert into setmeal_dish
    (setmeal_id,dish_id,name,price,copies)
    values
    <foreach collection="setmealDishes" item="sd" separator=",">
        (#{sd.setmealId},#{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})
    </foreach>
</insert>

分页查询

需求分析

  • 根据页码进行分页展示
  • 每页展示10条数据
  • 按照套餐名称 分类进行查询

代码开发

Controller层

  • 在SetmealController层中创建分页查询方法
@GetMapping("/page")
    @ApiOperation("套餐分页查询")
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
        PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
        return Result.success(pageResult);
    }

Service层

  • 在SetmealService中声明方法
PageResult pageQuery(SetmealPageQueryDto setmealPageQueryDto)
  • 在实现类中实现具体逻辑
@Override
    public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
        int pageNum = setmealPageQueryDTO.getPage();
        int pageSize = setmealPageQueryDTO.getPageSize();

        PageHelper.startPage(pageNum, pageSize);
        Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
        return new PageResult(page.getTotal(), page.getResult());

    }

Mapper层

  • 创建方法
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO)
  • 在对应的xml文件中添加查询语句
<select id="pageQuery" resultType="com.sky.vo.SetmealVO">
    select
    	s.*,c.name categoryName
    from
    	setmeal s
    left join
    	category c
    on
    	s.category_id = c.id
    <where>
        <if test="name != null">
            and s.name like concat('%',#{name},'%')
        </if>
        <if test="status != null">
            and s.status = #{status}
        </if>
        <if test="categoryId != null">
            and s.category_id = #{categoryId}
        </if>
    </where>
    order by s.create_time desc
</select>


删除套餐实现

需求分析

  • 起售的套餐不能删除
  • 可以批量删除

代码开发

Controller层

  • 在SetmealController层中添加方法
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids){
    setmealService.deleteBatch(ids);
    return Result.success();
}

Service层

  • 在SermealService层中声明方法
void deleteBatch(List<Long> ids);
  • 实现类中实现
@Transactional
    @Override
    public void deleteBatch(List<Long> ids) {
        //查询套餐是否在售卖
        ids.forEach(id -> {
            Setmeal setmeal = setmealMapper.getById(id);
            if(StatusConstant.ENABLE == setmeal.getStatus()){
                //起售中的套餐不能删除
                throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
            }
        });

        ids.forEach(setmealId -> {
            //删除套餐表中的数据
            setmealMapper.deleteById(setmealId);
            //删除套餐菜品关系表中的数据
            setmealDishMapper.deleteBySetmealId(setmealId);
        });
    }

Mapper层

  • 在setmealMapper层中添加getById方法和deleteById方法
  • 简单语句用注解实现
@Select("select * from setmeal where id = #{id}")
    Setmeal getById(Long id);

    @Delete("delete from setmeal where id = #{id}")
    void deleteById(Long setmealId);
  • 根据套餐id删除套餐和菜品的关联关系
  • 在SetmealDishMapper中创建方法
@Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
void deleteBySetmealId(Long setmealId);

套餐修改

需求分析

  • 先根据id查询到套餐 回显数据
  • 然后进行修改操作

代码实现

Controller层

  • 先添加根据id查询的方法
  • 再添加修改套餐方法
@GetMapping("/{id}")
    @ApiOperation("根据id查询套餐")
    public Result<SetmealVO> getById(@PathVariable Long id){
        SetmealVO setmealVO = setmealService.getByIdWithDish(id);
        return Result.success(setmealVO);
    }

    @PutMapping
    @ApiOperation("修改套餐")
    public Result update(@RequestBody SetmealDTO setmealDTO){
        setmealService.updateWithDish(setmealDTO);
        return Result.success();
    }

Service层

  • 在SetmealService层中创建两个方法
SetmealVO getByIdWithDish(Long id);

 void updateWithDish(SetmealDTO setmealDTO);
  • 在实现类中实现
@Override
    //根据id查询套餐
    public SetmealVO getByIdWithDish(Long id) {
        Setmeal setmeal = setmealMapper.getById(id);
        List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id);

        SetmealVO setmealVO = new SetmealVO();
        BeanUtils.copyProperties(setmeal, setmealVO);
        setmealVO.setSetmealDishes(setmealDishes);

        return setmealVO;
    }

    @Override
    @Transactional
    //修改套餐
    public void updateWithDish(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层

  • 在setmealMapper层中创建update方法
void update(Setmeal setmeal)
  • 在SetmealDIshMapper层中创建getBySetmealId方法
 @Select("select * from setmeal_dish where setmeal_id = #{setmealId}")
    List<SetmealDish> getBySetmealId(Long setmealId);
  • 在SetmealMapper.xml文件中实现动态sql
<update id="update" parameterType="com.sky.entity.Setmeal">
    update setmeal
    <set>
        <if test="name != null">name = #{name},</if>
        <if test="categoryId != null">category_id = #{categoryId},</if>
        <if test="price != null">price = #{price},</if>
        <if test="status != null">status = #{status},</if>
        <if test="description != null">description = #{description},</if>
        <if test="image != null">image = #{image},</if>
        <if test="updateTime != null">update_time = #{updateTime},</if>
        <if test="updateUser != null">update_user = #{updateUser},</if>
    </set>
    where id = #{id}
</update>

起售停售套餐

需求分析

  • 套餐中所有菜品起售时才可起售
  • 停售的套餐不能展示

代码开发

Controller层

  • 在setmealController中创建方法
@PostMapping("/status/{status}")
    @ApiOperation("套餐起售停售")
    public Result startOfstop(@PathVariable Integer status , long id){
        setmealService.startOfstop(status,id);
        return Result.success();
    }

Service层

  • 在setmealService中声明方法
void startOfstop(Integer status, long id);
  • 在实现类中实现
 @Override
    public void startOfstop(Integer status, long id) {
        //先判断套餐内是否包括停售的菜品
        if (status == StatusConstant.ENABLE) {
            List<Dish> dishList = dishMapper.getBySetmealId(id);
            if (dishList.size() > 0) {
                dishList.forEach(dish -> {
                    if (dish.getStatus() == StatusConstant.DISABLE) {
                        //如果套餐内有停售的菜品,不能起售
                        throw new DeletionNotAllowedException(MessageConstant.SETMEAL_CONTAIN_DISABLE_DISH);
                    }
                });
                Setmeal setmeal = Setmeal.builder()
                        .id(id)
                        .status(status)
                        .build();
                setmealMapper.update(setmeal);

            }
        }
    }

Mapper层

  • 在setmealMapper中创建方法
  • 简单语句直接用注解实现
@Select("select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = #{setmealId}")
List<Dish> getBySetmealId(Long setmealId);
  • debug日志
    • 联调时发现起售停售无法正常实现
    • 检查发现菜品的起售停售都没有对应接口
    • ?我忘记写了
    • 在dishController层和service层添加对应方法
    • 调用现有的update方法成功解决
  • debug日志2
    • 前后端联调的时候点击停售不显示异常
    • 实际上套餐状态没有更新
    • 在SetmealServiceImpl的startOfstop方法中
    • 只有起售(status == ENABLE)会更新状态
    • 停售时不更新
    • 将更新套餐状态部分代码从判断中提出解决

想温柔的对待这个世界