1.1 新增套餐需求分析

后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。

外卖系统数据库架构图 外卖管理系统数据流图_java

1.2 新增套餐数据模型

新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据。所以在新增套餐时,涉及到两个表:


说明

备注

setmeal

套餐表

存储套餐的基本信息

setmeal_dish

套餐菜品关系表

存储套餐关联的菜品的信息(一个套餐可以关联多个菜品)

1). 套餐表setmeal

外卖系统数据库架构图 外卖管理系统数据流图_spring boot_02

在该表中,套餐名称name字段是不允许重复的,在建表时,已经创建了唯一索引。

外卖系统数据库架构图 外卖管理系统数据流图_spring_03

2). 套餐菜品关系表setmeal_dish

外卖系统数据库架构图 外卖管理系统数据流图_外卖系统数据库架构图_04

在该表中,菜品的名称name,菜品的原价price 实际上都是冗余字段,因为在这张表中存储了菜品的ID(dish_id),根据该ID就可以查询出name与price的数据信息,而这里又存储了name,price。在后续的查询展示操作中,就不需要再去查询数据库获取菜品名称和原价了,这样可以简化操作。

1.3 新增套餐准备工作

在开发业务功能前,先将需要用到的类和接口基本结构创建好,无需准备Setmeal的相关实体类、Mapper接口、Service接口及实现,因为之前在做分类管理的时候,已经引入了Setmeal的相关基础代码。 接下来,我们就来完成以下的几步准备工作:

 1). 实体类 SetmealDish

package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 套餐菜品关系
 */
@Data
public class SetmealDish implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //套餐id
    private Long setmealId;


    //菜品id
    private Long dishId;


    //菜品名称 (冗余字段)
    private String name;

    //菜品原价
    private BigDecimal price;

    //份数
    private Integer copies;


    //排序
    private Integer sort;


    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否删除
    private Integer isDeleted;
}

2). DTO SetmealDto

该数据传输对象DTO,主要用于封装页面在新增套餐时传递过来的json格式的数据,其中包含套餐的基本信息,还包含套餐关联的菜品集合。

package com.itheima.reggie.dto;

import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.entity.SetmealDish;
import lombok.Data;
import java.util.List;

@Data
public class SetmealDto extends Setmeal {

    private List<SetmealDish> setmealDishes;

    private String categoryName;
}

3). Mapper接口 SetmealDishMapper

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.SetmealDish;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SetmealDishMapper extends BaseMapper<SetmealDish> {
}

 4). 业务层接口 SetmealDishService

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.SetmealDish;


public interface SetmealDishService extends IService<SetmealDish> {

}

5). 业务层实现类 SetmealDishServiceImpl

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.SetmealDish;
import com.itheima.reggie.mapper.SetmealDishMapper;
import com.itheima.reggie.service.SetmealDishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Description: new java files header..
 *
 * @author w
 * @version 1.0
 * @date 2022/8/19 15:35
 */
@Service
@Slf4j
public class SetmealDishServiceImpl extends ServiceImpl<SetmealDishMapper, SetmealDish> implements SetmealDishService {


}

6). 控制层 SetmealController

套餐管理的相关业务,我们都统一在 SetmealController 中进行统一处理操作。

package com.itheima.reggie.controller;

import com.itheima.reggie.common.R;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.service.SetmealDishService;
import com.itheima.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Description: 套餐管理
 * @version 1.0
 * @date 2022/8/19 15:37
 */

@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {
    @Autowired
    private SetmealService setmealService;
    @Autowired
    private SetmealDishService setmealDishService;

    @PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){
        /**@Description: 新增套餐
         * @version v1.0
         * @author LiBiGo
         * @date 2022/8/19 16:04
         */
        log.info("套餐信息:{}",setmealDto);

        setmealService.saveWithDish(setmealDto);

        return R.success("新增套餐成功");
    }

}

1.4 新增套餐时前端页面和服务端的交互过程

1). 点击新建套餐按钮,访问页面(backend/page/combo/add.html),页面加载发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中(==已实现==)

外卖系统数据库架构图 外卖管理系统数据流图_spring boot_05

获取套餐分类列表的功能我们不用开发,之前已经开发完成了,之前查询时type传递的是1,查询菜品分类; 本次查询时,传递的type为2,查询套餐分类列表。

2). 访问页面(backend/page/combo/add.html),页面加载时发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中(==已实现==)

外卖系统数据库架构图 外卖管理系统数据流图_spring boot_06

本次查询分类列表,传递的type为1,表示需要查询的是菜品的分类。查询菜品分类的目的,是添加套餐关联的菜品时,我们需要根据菜品分类,来过滤查询菜品信息。查询菜品分类列表的代码已经实现, 具体展示效果如下:

外卖系统数据库架构图 外卖管理系统数据流图_spring_07

 3). 当点击添加菜品窗口左侧菜单的某一个分类, 页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中

外卖系统数据库架构图 外卖管理系统数据流图_后端_08

4). 页面发送请求进行图片上传,请求服务端将图片保存到服务器(==已实现==)

5). 页面发送请求进行图片下载,将上传的图片进行回显(==已实现==)

外卖系统数据库架构图 外卖管理系统数据流图_后端_09

 6). 点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端

外卖系统数据库架构图 外卖管理系统数据流图_java_10

经过上述的页面解析及流程分析,发送这里需要发送的请求有5个,分别是 :

A. 根据传递的参数,查询套餐分类列表(已实现)

B. 根据传递的参数,查询菜品分类列表(已实现)

C. 图片上传(已实现)

D. 图片下载展示(已实现)

E. 根据菜品分类ID,查询菜品列表

1.5 新增套餐需要开发的功能

1). 根据分类ID查询菜品列表

请求

说明

请求方式

GET

请求路径

/dish/list

请求参数

?categoryId=1397844263642378242

2). 保存套餐信息

请求

说明

请求方式

POST

请求路径

/setmeal

请求参数

json格式数据

传递的json格式数据如下:

{
    "name":"营养超值工作餐",
    "categoryId":"1399923597874081794",
    "price":3800,
    "code":"",
    "image":"9cd7a80a-da54-4f46-bf33-af3576514cec.jpg",
    "description":"营养超值工作餐",
    "dishList":[],
    "status":1,
    "idType":"1399923597874081794",
    "setmealDishes":[
    	{"copies":2,"dishId":"1423329009705463809","name":"米饭","price":200},
    	{"copies":1,"dishId":"1423328152549109762","name":"可乐","price":500},
    	{"copies":1,"dishId":"1397853890262118402","name":"鱼香肉丝","price":3800}
    ]
}

1.6 根据分类查询菜品--代码开发

1.5.1 根据分类查询菜品

在当前的需求中,只需要根据页面传递的菜品分类的ID(categoryId)来查询菜品列表即可,因此直接定义一个DishController的方法,声明一个Long类型的categoryId,这样做是没问题的。

但是考虑到该方法的拓展性,我们在这里定义方法时,通过Dish这个实体来接收参数。

在DishController中定义方法list,接收Dish类型的参数:

在查询时,需要根据菜品分类categoryId进行查询,并且还要限定菜品的状态为起售状态(status为1),然后对查询的结果进行排序。

package com.itheima.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.reggie.common.R;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.service.CategoryService;
import com.itheima.reggie.service.DishFlavorService;
import com.itheima.reggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Description: 菜品管理 菜品及菜品口味的相关操作,统一使用这一个controller即可。
 * @version 1.0
 * @date 2022/8/18 11:08
 */

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private DishFlavorService dishFlavorService;
    @Autowired
    private CategoryService categoryService;

    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){
        /**@Description: 新增菜品
         * @author LiBiGo
         * @date 2022/8/18 11:44
         */
        log.info(dishDto.toString());
        dishService.saveWithFlavor(dishDto);

        return R.success("新增菜品成功");
    }

    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        /**@Description: 菜品信息分页查询
         * @author LiBiGo
         *
         * 数据库查询菜品信息时,获取到的分页查询结果 Page 的泛型为 Dish,而最终需要给前端页面返回的类型为DishDto,
         * 所以这个时候就要进行转换,基本属性直接通过属性拷贝的形式对Page中的属性进行复制,
         * 对于结果列表 records属性需要进行特殊处理的(需要封装菜品分类名称);
         *
         * @date 2022/8/19 10:41
         */

        // 构造分页构造器对象
        Page<Dish> pageInfo = new Page<>(page,pageSize);
        Page<DishDto> dishDtoPage = new Page<>();

        // 条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        // 添加过滤条件
        queryWrapper.like(name!=null,Dish::getName,name);
        // 添加排序条件
        queryWrapper.orderByDesc(Dish::getUpdateTime);

        // 执行分页查询
        dishService.page(pageInfo,queryWrapper);

        // 对象的拷贝
        BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");

        List<Dish> records = pageInfo.getRecords();

        List<DishDto> list = records.stream().map((item) -> {
            DishDto dishDto = new DishDto();

            BeanUtils.copyProperties(item,dishDto);

            Long categoryId = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);

            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            return dishDto;
        }).collect(Collectors.toList());

        dishDtoPage.setRecords(list);

        return R.success(dishDtoPage);
    }

    @GetMapping("/{id}")
    public R<DishDto> get(@PathVariable Long id){
        /**@Description: 根据id查询菜品信息和对应的口味信息
         * @author LiBiGo
         * @date 2022/8/19 11:43
         */

        DishDto dishDto = dishService.getByIdWithFlavor(id);
        return R.success(dishDto);
    }

    @PutMapping
    // @PathVariable : 该注解可以用来提取url路径中传递的请求参数。
    public R<String> update(@RequestBody DishDto dishDto){
        /**@Description: 修改菜品
         * @author LiBiGo
         * @date 2022/8/19 11:58
         */
        log.info(dishDto.toString());

        dishService.updateWithFlavor(dishDto);

        return R.success("新增菜品成功");
    }

    @GetMapping("/list")
    public R<List<Dish>> list(Dish dish){
        /**@Description: 根据条件查询对应的菜品数
         * @author LiBiGo
         * @date 2022/8/19 15:49
         */
        // 构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
        //添加条件,查询状态为1(起售状态)的菜品
        queryWrapper.eq(Dish::getStatus,1);

        //  添加排序条件
        queryWrapper.orderByDesc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        List<Dish> list = dishService.list(queryWrapper);

        return R.success(list);
    }

}

1.5.2 根据分类查询菜品功能测试

代码编写完毕,我们重新启动服务器,进行测试,可以通过debug断点跟踪的形式查看页面传递的参数封装情况,及响应给页面的数据信息。

外卖系统数据库架构图 外卖管理系统数据流图_java_11

1.7 保存套餐功能--代码开发

1.7.1 逻辑分析

在进行套餐信息保存时,前端提交的数据,不仅包含套餐的基本信息,还包含套餐关联的菜品列表数据 setmealDishes。

解决方案:需要在Setmeal的基本属性的基础上,再扩充一个属性 setmealDishes来接收页面传递的套餐关联的菜品列表,

1). SetmealController中定义方法save,新增套餐

package com.itheima.reggie.controller;

import com.itheima.reggie.common.R;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.service.SetmealDishService;
import com.itheima.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Description: 套餐管理
 * 不仅需要保存套餐的基本信息,还需要保存套餐关联的菜品数据,所以需要再该方法中调用业务层方法,完成两块数据的保存。
 * @version 1.0
 * @date 2022/8/19 15:37
 */

@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {
    @Autowired
    private SetmealService setmealService;
    @Autowired
    private SetmealDishService setmealDishService;

    @PostMapping
    // 页面传递的数据是json格式,需要在方法形参前面加上@RequestBody注解, 完成参数封装。
    public R<String> save(@RequestBody SetmealDto setmealDto){
        /**@Description: 新增套餐
         * @version v1.0
         * @author LiBiGo
         * @date 2022/8/19 16:04
         */
        log.info("套餐信息:{}",setmealDto);

        setmealService.saveWithDish(setmealDto);

        return R.success("新增套餐成功");
    }

}

 2). SetmealService中定义方法saveWithDish

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;

public interface SetmealService extends IService<Setmeal> {
    // 新增套餐,同时需要保存套餐和菜品的关联关系
    public void saveWithDish(SetmealDto setmealDto);
}

3). SetmealServiceImpl实现方法saveWithDish

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.entity.SetmealDish;
import com.itheima.reggie.mapper.SetmealMapper;
import com.itheima.reggie.service.SetmealDishService;
import com.itheima.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Description: new java files header..
 *
 * @author w
 * @version 1.0
 * @date 2022/8/16 10:17
 */
@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {

    @Autowired
    private SetmealDishService setmealDishService ;

    @Transactional
    @Override
    public void saveWithDish(SetmealDto setmealDto) {
        /**@Description: 新增套餐,同时需要保存套餐和菜品的关联关系
         *
         * A. 保存套餐基本信息
         * B. 获取套餐关联的菜品集合,并为集合中的每一个元素赋值套餐ID(setmealId)
         * C. 批量保存套餐关联的菜品集合
         *
         * @author LiBiGo
         * @date 2022/8/19 16:10
         */
        // 保存套餐的基本信息,操作setmeal,执行insert操作
        this.save(setmealDto);

        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map((item) -> {
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

        // 保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
        setmealDishService.saveBatch(setmealDishes);

    }
}

1.7.2 功能测试

代码编写完毕,重新启动服务器,进行测试,可以通过debug断点跟踪的形式查看页面传递的参数封装情况及套餐相关数据的保存情况。