苍穹外卖day04·实战篇

1.分类id查询与新增套餐

需求分析与设计

请添加图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分类id查询业务规则:

  • 套餐名称唯一
  • 套餐必须属于某个分类
  • 套餐必须包含菜品
  • 名称、分类、价格、图片为必填项
  • 添加菜品窗口需要根据分类类型来展示菜品
  • 新增的套餐默认为停售状态

接口设计(共涉及到4个接口):

  • 根据类型查询分类(已完成)
  • 根据分类id查询菜品
  • 图片上传(已完成)
  • 新增套餐

2.套餐分页查询

需求分析与设计

在这里插入图片描述
在这里插入图片描述

业务规则:

  • 根据页码进行分页展示
  • 每页展示10条数据
  • 可以根据需要,按照套餐名称、分类、售卖状态进行查询

3.删除套餐

需求分析与设计

在这里插入图片描述
在这里插入图片描述

业务规则:

  • 可以一次删除一个套餐,也可以批量删除套餐
  • 起售中的套餐不能删除

4.修改套餐

需求分析与设计

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接口设计(共涉及到5个接口):

  • 根据id查询套餐
  • 根据类型查询分类(已完成)
  • 根据分类id查询菜品(已完成)
  • 图片上传(已完成)
  • 修改套餐

5.起售停售套餐

需求分析与设计

在这里插入图片描述
在这里插入图片描述
业务规则:

  • 可以对状态为起售的套餐进行停售操作,可以对状态为停售的套餐进行起售操作
  • 起售的套餐可以展示在用户端,停售的套餐不能展示在用户端
  • 起售套餐时,如果套餐内包含停售的菜品,则不能起售

代码实现

DIshController层

/**
    用list根据分类 ID 查询菜品,通常会返回多个菜品
    */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<Dish>> list(Long categoryId){
        List<Dish> list = dishService.list(categoryId);
        return Result.success(list);
    }

DishService接口

    /**
     * 根据分类id查询菜品
     *
     * @param categoryId
     * @return
     */
    List<Dish> list(Long categoryId);

DishServiceImpl层

/**
     * 根据分类id查询菜品
     * @param categoryID
     * @return
     */
    public List<Dish> list(Long categoryID){
        Dish dish = Dish.builder()
                .categoryId(categoryID)
                .status(StatusConstant.ENABLE)
                .build();
        return dishMapper.list(dish);
    }

DishMapper层

/**
     * 动态条件查询菜品
     * @param dish
     * @return
     */
    List<Dish> list(Dish dish);


    /**
     * 根据套餐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 = #{setmealId}")
    List<Dish> getBySetmealId(Long id);

DishMapper.xml

<select id="list" resultType="com.sky.entity.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>

SetmealController层

package com.sky.controller.admin;

import com.sky.dto.SetmealDTO;
import com.sky.dto.SetmealPageQueryDTO;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import com.sky.vo.SetmealVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/admin/setmeal")
@Slf4j
@Api(tags = "套餐相关接口")
public class SetmealController {

    @Autowired
    public SetmealService setmealService;

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

    @GetMapping("/page")
    @ApiOperation("分页查询")
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
         PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
         return Result.success(pageResult);
    }

    @DeleteMapping
    @ApiOperation("删除套餐")
                                     //用List<Long>可以批量删除套餐
    public Result delete(@RequestParam List<Long> ids) {
        setmealService.deleteBatch(ids);
        return Result.success();
    }

    @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.update(setmealDTO);
        return Result.success();
    }

    @PostMapping("/status/{status}")
    @ApiOperation("启停售")
    public Result<String> stopAndStart(@PathVariable Integer status,Long id) {
        setmealService.stopAndStart(status, id);
        return Result.success();
    }
}

SetmealService接口

package com.sky.service;

import com.sky.dto.SetmealDTO;
import com.sky.dto.SetmealPageQueryDTO;
import com.sky.result.PageResult;
import com.sky.vo.SetmealVO;

import java.util.List;

public interface SetmealService {

    /**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     * @param setmealDTO
     */
    void saveWithDish(SetmealDTO setmealDTO);

    /**
     * 分页查询
     * @param setmealPageQueryDTO
     * @return
     */
    PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

    /**
     * 删除套餐
     * @param ids
     */
    void deleteBatch(List<Long> ids);

    /**
     * 根据id查询套餐
     * @param id
     * @return
     */
    SetmealVO getByIdWithDish(Long id);

    /**
     * 修改套餐
     * @param setmealDTO
     */
    void update(SetmealDTO setmealDTO);

    /**
     * 启停售
     * @param status
     * @param id
     */
    void stopAndStart(Integer status, Long id);
}

SetmealServiceImpl层

package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.dto.SetmealDTO;
import com.sky.dto.SetmealPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.entity.Setmeal;
import com.sky.entity.SetmealDish;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealDishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import com.sky.vo.SetmealVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;


@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {

    @Autowired
    public SetmealMapper setmealMapper;
    @Autowired
    private SetmealDishMapper setmealDishMapper;
    @Autowired
    private DishMapper dishMapper;

    /**
     * 新增套餐
     *
     * @param setmealDTO
     */
    @Transactional
    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);
    }


    /**
     * 分页
     * @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());
    }

    /**
     * 删除套餐
     * @param ids
     */
    @Transactional
    public void deleteBatch(List<Long> ids) {

        /**
         * 当未选中套餐进行空删时,系统会显示删除成功,应该返回未选择要删除的套餐
         * 优化:进行非空校验
          */
        if (ids == null || ids.size() == 0) {
            throw new DeletionNotAllowedException(MessageConstant.SETMEAL_NOT_SELECTED);
        }

        ids.forEach(id -> {
            //根据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);
        });
    }


    /**
     * 根据id查询套餐和套餐菜品关系
     * @param id
     * @return
     */
    public SetmealVO getByIdWithDish(Long id) {
        Setmeal setmeal = setmealMapper.getById(id);
        List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id);
        //将查询到的数据封装到VO(前端)
        SetmealVO setmealVO = new SetmealVO();
        BeanUtils.copyProperties(setmeal, setmealVO);
        setmealVO.setSetmealDishes(setmealDishes);
        return setmealVO;
    }

    /**
     * 修改套餐
     * @param setmealDTO
     */
    @Transactional
    public void update(SetmealDTO setmealDTO) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO, setmeal);
        //修改套餐表
        setmealMapper.update(setmeal);


        //套餐id  从前端获取当前套餐的id
        Long setmealId = setmealDTO.getId();

        //删除套餐和菜品的关联关系,操作setmeal_dish表,执行delete
        setmealDishMapper.deleteBySetmealId(setmealId);
        //从 setmealDTO 中拿到用户新选择的菜品列表(setmealDishes),然后为每一个菜品都设置上当前套餐的 ID。
        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
        });
        //重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行Insert
        setmealDishMapper.insertBatch(setmealDishes);
    }

    /**
     * 启停售
     * @param status
     * @param id
     */
   
    public void stopAndStart(Integer status, Long id) {
        //遍历这个套餐下所有的菜品:
        //如果其中有任何一道菜是“禁用”状态(比如被下架了),
        //那就不能把整个套餐“启用”,因为这样会导致套餐中包含了不能卖的菜品。
        //抛出异常提示:“套餐启用失败,包含禁售菜品”。
        if (status == StatusConstant.ENABLE) {
            List<Dish> dishList = dishMapper.getBySetmealId(id);
            if (dishList != null && dishList.size() > 0) {
                dishList.forEach(dish -> {
                    if(StatusConstant.DISABLE == dish.getStatus()) {
                        throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ENABLE_FAILED);
                    }
                });
            }
        }
        Setmeal setmeal = Setmeal.builder()
                .id(id)
                .status(status)
                .build();
        setmealMapper.update(setmeal);
    }
}

SetmealMapper层

package com.sky.mapper;

import com.github.pagehelper.Page;
import com.sky.annotation.AutoFill;
import com.sky.dto.SetmealPageQueryDTO;
import com.sky.entity.Setmeal;
import com.sky.enumeration.OperationType;
import com.sky.vo.SetmealVO;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface SetmealMapper {

    /**
     * 根据分类id查询套餐的数量
     * @param id
     * @return
     */
    @Select("select count(id) from setmeal where category_id = #{categoryId}")
    Integer countByCategoryId(Long id);

    /**
     * 根据套餐id修改套餐信息
     * @param setmeal
     */
    @AutoFill(OperationType.UPDATE)
    void update(Setmeal setmeal);


    /**
     * 添加套餐
     * @param setmeal
     */
    @AutoFill(OperationType.INSERT)
    void insert(Setmeal setmeal);

    /**
     * 分页查询
     * @param setmealPageQueryDTO
     * @return
     */
    Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

    /**
     * 通过id查询套餐
     * @param id
     * @return
     */
    @Select("select * from setmeal where id =#{id}")
    Setmeal getById(Long id);

    /**
     * 通过id删除套餐
     * @param setmealId
     */
    @Delete("delete from setmeal where id = #{id}")
    void deleteById(Long setmealId);
}

SetmealMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.sky.mapper.SetmealMapper">


    <!--    根据套餐id修改套餐信息-->
    <update id="update">
        update setmeal
        <set>
            <if test="categoryId != null">
                category_id = #{categoryId},
            </if>
            <if test="name != null">
                name = #{name},
            </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>

    <!--添加套餐-->
    <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>

    <!--分页-->
    <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>
</mapper>

SetmealDishMapper层

package com.sky.mapper;

import com.sky.entity.SetmealDish;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.ArrayList;
import java.util.List;

/**
 * @author lam
 * @data 2025/5/22 下午3:36
 * @description
 */
@Mapper
public interface SetmealDishMapper {

    /**
     * 根据菜品id查询对应的套餐id
     * @param ids
     * @return
     */
    //                                                  用in可将多个菜品id动态的菜品id拼接进去
    //select setmeal if from setmeal_dish where dish_id in (1,2,3,4)

    List<Long> getSetmealIdsByDishIds(List<Long> ids);


    /**
     * 批量保存套餐和菜品的关联关系
     * @param setmealDishes
     */
    void insertBatch(List<SetmealDish> setmealDishes);

    /**
     * 根据套餐id删除套餐和菜品的关联关系
     * @param setmealId
     */
    @Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
    void deleteBySetmealId(Long setmealId);


    /**
     * 根据套餐id查询套餐和菜品的关联关系
     * @param id
     * @return
     */
    @Select("select * from setmeal_dish where setmeal_id = #{setmealId}")
    List<SetmealDish> getBySetmealId(Long id);
}

SetmealDishMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.sky.mapper.SetmealDishMapper">


    <select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
        select setmeal_id from setmeal_dish where dish_id in
        <foreach collection="ids" separator="," open="(" close=")" item="dishId">
            #{dishId}
        </foreach>
    </select>

    <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>
</mapper>

代码优化

当未选中套餐进行空删时,系统会显示删除成功,应该返回未选择要删除的套餐

SetmealServiceImpl的删除套餐中
进行非空校验
if (ids == null || ids.size() == 0) {
            throw new DeletionNotAllowedException(MessageConstant.SETMEAL_NOT_SELECTED);
        }MessageConstant中添加
public static final String SETMEAL_NOT_SELECTED = "未选择要删除的套餐";

总结

  • 修改数据前为什么先查
  1. 前端页面需要展示原始数据(回显)
    用户在点击“编辑”按钮时,系统需要把该套餐当前的信息显示出来。
    这样用户才能知道哪些内容是可以改的,以及改之前是什么样子。
    比如:套餐名称、价格、状态、关联的菜品等。
  2. 服务端需要原始数据用于后续处理
    在更新操作中,有些字段不能由前端传入,必须从数据库中读取后使用。
    例如:创建时间(create_time)
    商家 ID(shop_id)
    原始状态(避免误操作启用/禁用)
  3. 校验数据是否存在、是否允许修改
    修改前先查一次数据库,可以判断该套餐是否存在。
    避免无效或非法请求造成错误。
  4. 确保关联数据一致性(如套餐与菜品的关系)
    修改套餐时,可能需要重新设置套餐包含的菜品。
    因此不仅要查套餐本身,还要查它原来包含的菜品列表。
    以便对比新增、删除或保留哪些菜品。
  • @Transactional 在单个插入/更新/删除操作

  • @PathVariable:
    主要用于访问特定资源,如通过 ID 获取某个对象(例如,/users/{userId})。

  • @RequestParam:
    用于处理查询字符串中的参数(例如,分页查询中的 page 和 size 参数)。

  • @RequestBody:
    当你需要发送复杂的数据结构(比如 JSON 对象)到服务器时使用,通常用于 POST 或 PUT 请求。

  • 在Impl业务层中:是否需要返回值,完全取决于调用方是否需要知道这个方法的“结果”。如果是查询、获取列表等操作,就需要返回;如果是插入、更新、删除等操作,可以根据业务需要决定是否返回关键信息。
    ![](https://i-blog.csdnimg.cn/direct/996062a03b20431590d5eb63d3116307.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值