【学习日记2023.5.22】 之 套餐模块完善

4. 功能模块完善之套餐模块

4.1 新增套餐

4.1.1 需求分析与设计

产品原型

后台系统中可以管理套餐信息,通过 新增功能来添加一个新的套餐,在添加套餐时需要添加套餐对应菜品的信息,并且需要上传套餐图片。

新增套餐原型:
【学习日记2023.5.22】 之 套餐模块完善_第1张图片
【学习日记2023.5.22】 之 套餐模块完善_第2张图片

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

业务规则:

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

接口设计

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

根据分类id查询菜品
【学习日记2023.5.22】 之 套餐模块完善_第3张图片

新增套餐
【学习日记2023.5.22】 之 套餐模块完善_第4张图片

数据库设计:

setmeal表为套餐表,用于存储套餐的信息。具体表结构如下:

字段名 数据类型 说明 备注
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 菜品份数

4.1.2 代码开发

DishController

/**
     * 根据分类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);
}

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);

DishMapper.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>

SetmealController

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

    @Autowired
    private SetmealService setmealService;

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

SetmealService

public interface SetmealService {

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

SetmealServiceImpl

/**
 * 套餐业务实现
 */
@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {

    @Autowired
    private 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);
    }
}

SetmealMapper

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

SetmealMapper.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>

SetmealDishMapper

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

SetmealDishMapper.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>

4.1.3 功能测试

接口文档测试

  • 根据分类id查询菜品
    【学习日记2023.5.22】 之 套餐模块完善_第5张图片

  • 新增套餐
    【学习日记2023.5.22】 之 套餐模块完善_第6张图片

前后端联调

  • 根据分类id查询菜品
    【学习日记2023.5.22】 之 套餐模块完善_第7张图片

  • 新增套餐
    【学习日记2023.5.22】 之 套餐模块完善_第8张图片

4.1.4 提交代码

commit—>describe—>push

4.2 套餐分页查询

4.2.1 需求分析与设计

产品原型

系统中的套餐数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

套餐分页原型:
【学习日记2023.5.22】 之 套餐模块完善_第9张图片

在套餐列表展示时,除了套餐的基本信息(名称、售价、售卖状态、最后操作时间)外,还有两个字段略微特殊,第一个是图片字段 ,从数据库查询出来的仅仅是图片的名字,图片要想在表格中回显展示出来,就需要下载这个图片。第二个是套餐分类,这里展示的是分类名称,而不是分类ID,此时就需要根据套餐的分类ID,去分类表中查询分类信息,然后在页面展示。

业务规则:

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

接口设计
【学习日记2023.5.22】 之 套餐模块完善_第10张图片

4.2.2 代码开发

SetmealController

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

SetmealService

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

SetmealServiceImpl

/**
     * 分页查询
     * @param setmealPageQueryDTO
     * @return
*/
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());
}

SetmealMapper

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

SetmealMapper.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>

4.2.3 功能测试

接口文档测试和前后端联调测试
【学习日记2023.5.22】 之 套餐模块完善_第11张图片
【学习日记2023.5.22】 之 套餐模块完善_第12张图片

4.2.4 提交代码

commit—>describe—>push

4.3 删除套餐

4.3.1 需求分析与设计

产品原型:
【学习日记2023.5.22】 之 套餐模块完善_第13张图片

业务规则:

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

接口设计:
【学习日记2023.5.22】 之 套餐模块完善_第14张图片

4.3.2 代码开发

SetmealController

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

SetmealService

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

SetmealServiceImpl

/**
     * 批量删除套餐
     * @param ids
*/
@Transactional
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);
    });
}

SetmealMapper

/**
     * 根据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);

SetmealDishMapper

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

4.3.3 功能测试

请添加图片描述

接口文档测试

  • 删除起售中的套餐
    【学习日记2023.5.22】 之 套餐模块完善_第15张图片

  • 删除停售的套餐
    【学习日记2023.5.22】 之 套餐模块完善_第16张图片

前后端联调
【学习日记2023.5.22】 之 套餐模块完善_第17张图片
【学习日记2023.5.22】 之 套餐模块完善_第18张图片

4.3.4 提交代码

commit—>describe—>push

4.4 修改套餐

4.4.1 需求分析与设计

产品原型:
【学习日记2023.5.22】 之 套餐模块完善_第19张图片

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

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

根据id查询套餐
【学习日记2023.5.22】 之 套餐模块完善_第20张图片

修改套餐
【学习日记2023.5.22】 之 套餐模块完善_第21张图片

4.4.2 代码开发

SetmealController

/**
     * 根据id查询套餐,用于修改页面回显数据
     *
     * @param id
     * @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询套餐")
public Result<SetmealVO> getById(@PathVariable Long id) {
    SetmealVO setmealVO = setmealService.getByIdWithDish(id);
    return Result.success(setmealVO);
}

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

SetmealService

/**
     * 根据id查询套餐和关联的菜品数据
     * @param id
     * @return
*/
SetmealVO getByIdWithDish(Long id);

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

SetmealServiceImpl

/**
     * 根据id查询套餐和套餐菜品关系
     *
     * @param id
     * @return
*/
public SetmealVO getByIdWithDish(Long id) {
    SetmealVO setmealVO = setmealMapper.getByIdWithDish(id);
    return setmealVO;
}

/**
     * 修改套餐
     *
     * @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);
}

SetmealMapper

/**
     * 根据id查询套餐和套餐菜品关系
     * @param id
     * @return
*/
SetmealVO getByIdWithDish(Long id);

SetmealMapper.xml

<resultMap id="setmealAndDishMap" type="com.sky.vo.SetmealVO" autoMapping="true">
    <result column="id" property="id"/>
    <collection property="setmealDishes" ofType="SetmealDish">
        <result column="sd_id" property="id"/>
        <result column="setmeal_id" property="setmealId"/>
        <result column="dish_id" property="dishId"/>
        <result column="sd_name" property="name"/>
        <result column="sd_price" property="price"/>
        <result column="copies" property="copies"/>
    collection>
resultMap>
<select id="getByIdWithDish" parameterType="long" resultMap="setmealAndDishMap">
    select a.*,
            b.id    sd_id,
            b.setmeal_id,
            b.dish_id,
            b.name  sd_name,
            b.price sd_price,
            b.copies
    from setmeal a
    left join
    	setmeal_dish b
    on
    	a.id = b.setmeal_id
    where a.id = #{id}
select>

4.4.3 功能测试

接口文档测试

  • 根据id查询套餐
    【学习日记2023.5.22】 之 套餐模块完善_第22张图片

  • 修改套餐
    【学习日记2023.5.22】 之 套餐模块完善_第23张图片

前后端联调

  • 数据回显
    【学习日记2023.5.22】 之 套餐模块完善_第24张图片

  • 修改套餐
    【学习日记2023.5.22】 之 套餐模块完善_第25张图片

4.4.4 提交代码

commit—>describe—>push

4.5 起售停售套餐

4.5.1 需求分析与设计

产品原型:
【学习日记2023.5.22】 之 套餐模块完善_第26张图片

业务规则:

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

接口设计:
【学习日记2023.5.22】 之 套餐模块完善_第27张图片

4.5.2 代码开发

SetmealController

/**
     * 套餐起售停售
     * @param status
     * @param id
     * @return
*/
@PostMapping("/status/{status}")
@ApiOperation("套餐起售停售")
public Result startOrStop(@PathVariable Integer status, Long id) {
    setmealService.startOrStop(status, id);
    return Result.success();
}

SetmealService

/**
     * 套餐起售、停售
     * @param status
     * @param id
*/
void startOrStop(Integer status, Long id);

SetmealServiceImpl

/**
     * 套餐起售、停售
     * @param status
     * @param id
*/
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);
}

DishMapper

/**
     * 根据套餐id查询菜品
     * @param setmealId
     * @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 setmealId);

4.5.3 功能测试

请添加图片描述

起售、停售套餐

  • 起售:套餐内含未启用菜品
    【学习日记2023.5.22】 之 套餐模块完善_第28张图片

  • 停售
    【学习日记2023.5.22】 之 套餐模块完善_第29张图片

前后端联调

  • 起售
    【学习日记2023.5.22】 之 套餐模块完善_第30张图片

  • 停售
    【学习日记2023.5.22】 之 套餐模块完善_第31张图片

4.5.4 提交代码

commit—>describe—>push

你可能感兴趣的:(项目之外卖系统,学习,数据库,java)