学习【瑞吉外卖⑤】SpringBoot单体项目_套餐管理业务开发



  • 本文章发布在CSDN上,一是方便博主自己线上阅览,二是巩固自己所学知识。
  • 博客内容主要参考上述视频和资料,视频中出现的 PPT 内容大体也为本文所录。


  • 若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。


上一篇:学习【瑞吉外卖④】SpringBoot单体项目:https://blog.csdn.net/yanzhaohanwei/article/details/125138771


0.总目录



1.新增套餐功能


1.1.需求分析


套餐就是菜品的集合。

后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐。

在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片。

在移动端会按照套餐分类来展示对应的套餐。

1.新增套餐功能展示图


1.2.数据模型


新增套餐,其实就是将新增页面录入的套餐信息插入到 setmeal 表,还需要向 setmeal_dish 表插入套餐和菜品关联数据。

所以在新增套餐时,涉及到两个表:setmeal(套餐表)、setmeal_dish(套餐菜品关系表)


  • setmeal(套餐表)

2.套餐表结构


  • setmeal_dish(套餐菜品关系表)

3.套餐菜品关系表结构


1.3.代码开发


1.3.1.准备工作


在开发业务功能前,先将需要用到的类和接口基本结构创建好。

此外,Setmeal 相关的实体类、MapperService【瑞吉外卖③】分类业务管理开发中的 4.3.2 里已经创建过了,这里不再赘述。


实体类 SetmealDish

com/itheima/reggie/entity/SetmealDish.java

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

DTO 类 SetmealDto

com/itheima/reggie/dto/SetmealDto.java

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

Mapper 接口 SetmealDishMapper

com/itheima/reggie/mapper/SetmealMapper.java

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> {}

业务层接口 SetmealDishService

com/itheima/reggie/service/SetmealDishService.java

package com.itheima.reggie.service;

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

public interface SetmealDishService extends IService<SetmealDish> {}

业务层接口实现类 SetmealDishServicelmpl

com/itheima/reggie/service/impl/SetmealDishServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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;

@Service
@Slf4j
public class SetmealDishServiceImpl extends ServiceImpl<SetmealDishMapper, SetmealDish> implements SetmealDishService {}

控制层 SetmealController

com/itheima/reggie/service/SetmealDishService.java

package com.itheima.reggie.controller;

import com.itheima.reggie.service.CategoryService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 套餐管理
 */
@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {
    @Autowired
    private SetmealService setmealService;

    @Autowired
    private SetmealDishService setmealDishService;

    @Autowired
    private CategoryService categoryService;
}

1.3.2.梳理交互过程


在开发代码之前,需要梳理一下新增套餐时前端页面和服务端的交互过程。

  1. 页面(backend/page/combo/add.html)发送 ajax 请求,请求服务端获取套餐分类数据并展示到下拉框中。
  2. 页面发送 ajax 请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中。
  3. 页面发送 ajax 请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中
  4. 页面发送请求进行图片上传,请求服务端将图片保存到服务器。
  5. 页面发送请求进行图片下载,将上传的图片进行回显。
  6. 点击保存按钮,发送 ajax 请求,将套餐相关数据以 json 形式提交到服务端

开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可。

其中 1245 的交互我们在前面已经实现了。


1.3.3.根据菜品分类查询数据


1.3.3.1.前端分析

4.根据菜品分离查询对应菜品数据


1.3.3.2.代码编写

com/itheima/reggie/controller/DishController.java

/**
 * 根据条件查询对应的菜品数据
 *
 * @param dish
 * @return
 */
@GetMapping("/list")
public R<List<Dish>> list(Dish dish) {
    //构造查询条件
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<Dish>();
    //添加查询条件
    queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
    //添加排序条件
    queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
    //调用 service 方法拿到 list 集合
    List<Dish> list = dishService.list(queryWrapper);
    return R.success(list);
}

这里展示一下 Idea 控制台打印的 SQL 语句

SELECT id,name,category_id,price,code,image,description,status,sort,create_time,update_time,create_user,update_user \
	FROM dish \
	WHERE (category_id = ?) ORDER BY sort ASC,update_time DESC;

点击“添加菜品”按钮就会弹出“添加菜品”界面,此时界面中出现了具体菜品信息。

说明根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中的功能已经实现。

5.套餐管理_添加菜品_按钮

6.添加菜品界面


实体类 Dishcom/itheima/reggie/entity/Dish.java) 中存在 private Integer status; // 0 停售;1 起售 这样的字段。

所以我们需要加一些查询的条件,只查状态为 1 的菜品。

com/itheima/reggie/controller/DishController.java

queryWrapper.eq(Dish::getStatus, 1);

此时 idea 控制台上的输出语句

SELECT id,name,category_id,price,code,image,description,status,sort,create_time,update_time,create_user,update_user \
	FROM dish \
	WHERE (category_id = ? AND status = ?) ORDER BY sort ASC,update_time DESC;

1.3.4.新增套餐功能


1.3.4.1.前端分析

7.新增套餐_保存按钮_请求路径_请求方式

8.参数


1.3.4.2.代码编写

com/itheima/reggie/service/SetmealService.java

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

com/itheima/reggie/service/impl/SetmealServiceImpl.java

@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
    @Autowired
    private SetmealDishService setmealDishService;

    /**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     *
     * @param setmealDto
     */
    @Transactional
    public void saveWithDish(SetmealDto setmealDto) {
        //保存套餐的基本信息,操作 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);
    }
}

com/itheima/reggie/controller/SetmealController.java

/**
 * 新增套餐
 *
 * @param setmealDto
 * @return
 */
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto) {
    log.info("套餐信息:{}", setmealDto);
    setmealService.saveWithDish(setmealDto);
    return R.success("新增套餐成功");
}

idea 控制台上输出的 SQL 语句。

INSERT INTO setmeal \
( id, category_id, name, price, status, code, description, image, create_time, update_time, create_user, update_user ) \
 VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );
INSERT INTO setmeal_dish \
 ( id, setmeal_id, dish_id, name, price, copies, create_time, update_time, create_user, update_user )  \
 VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );

2.套餐分页查询功能


2.1.需求分析


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

一般的系统中都会以分页的方式来展示列表数据。


2.2.梳理交互过程


在开发代码之前,需要梳理一下套餐分页查询时前端页面和服务端的交互过程。

  1. 页面(backend/page/combo/list.html)发送 ajax 请求,将分页查询参数(pagepageSizename)提交到服务端,获取分页数据。

  2. 页面发送请求,请求服务端进行图片下载,用于页面图片展示。

开发套餐信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这 2 次请求即可。


2.3.前端分析


  • 页面(backend/page/combo/list.html)发送 ajax 请求,将分页查询参数(pagepageSizename)提交到服务端,获取分页数据。

9.分页查询套餐信息_前端分析


2.4.代码编写


com/itheima/reggie/controller/SetmealController.java

/**
 * 套餐分页查询
 *
 * @param page
 * @param pageSize
 * @param name
 * @return
 */
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
    //分页构造器对象
    Page<Setmeal> pageInfo = new Page<>(page, pageSize);
    Page<SetmealDto> dtoPage = new Page<>();

    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    //添加查询条件,根据name进行like模糊查询
    queryWrapper.like(name != null, Setmeal::getName, name);
    //添加排序条件,根据更新时间降序排列
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);

    setmealService.page(pageInfo, queryWrapper);

    //对象拷贝
    BeanUtils.copyProperties(pageInfo, dtoPage, "records");
    List<Setmeal> records = pageInfo.getRecords();

    List<SetmealDto> list = records.stream().map((item) -> {
        SetmealDto setmealDto = new SetmealDto();
        //对象拷贝
        BeanUtils.copyProperties(item, setmealDto);
        //分类 id
        Long categoryId = item.getCategoryId();
        //根据分类id查询分类对象
        Category category = categoryService.getById(categoryId);
        if (category != null) {
            //分类名称
            String categoryName = category.getName();
            setmealDto.setCategoryName(categoryName);
        }
        return setmealDto;
    }).collect(Collectors.toList());

    dtoPage.setRecords(list);
    return R.success(dtoPage);
}

3.删除套餐功能


3.1.需求分析


在套餐管理列表页面点击删除按钮,可以删除对应的套餐信息。

也可以通过复选框选择多个套餐,点击批量删除按钮一次删除多个套餐。

注意:对于状态为售卖中的套餐不能删除,需要先停售,然后才能删除。

10.套餐删除页面


3.2.梳理交互过程


在开发代码之前,需要梳理一下删除套餐时前端页面和服务端的交互过程

  1. 删除单个套餐时,页面发送 ajax 请求,根据套餐 id 删除对应套餐

11.删除单个套餐的请求链接

  1. 删除多个套餐时,页面发送 ajax 请求,根据提交的多个套餐 id 删除对应套餐

12,删除多个套餐的请求链接

开发删除套餐功能,其实就是在服务端编写代码去处理前端页面发送的这 2 次请求即可。

观察删除单个套餐和批量删除套餐的请求信息可以发现,两种请求的地址请求方式都是相同的。

不同的是传递的 id 个数,所以在服务端可以提供一个方法来统一处理。


3.3.代码编写


com/itheima/reggie/service/SetmealService.java

//删除套餐,同时需要删除套餐和菜品的关联数据
public void removeWithDish(List<Long> ids);

com/itheima/reggie/service/impl/SetmealServiceImpl.java

/**
 * 删除套餐,同时需要删除套餐和菜品的关联数据
 *
 * @param ids
 */
@Transactional
public void removeWithDish(List<Long> ids) {
    //查询套餐状态,确定是否可用删除
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<Setmeal>();

    //sql 语句:select count(*) from setmeal where id in (1,2,3) and status=1;
    queryWrapper.in(Setmeal::getId, ids);
    queryWrapper.eq(Setmeal::getStatus, 1);

    int count = this.count(queryWrapper);

    if (count > 0) {
        //如果不能删除,抛出一个业务异常
        throw new CustomException("套餐正在售卖中,不能删除");
    }

    //如果可以删除,先删除套餐表中的数据 --- setmeal
    this.removeByIds(ids);
    
    //sql 语句:delete from setmeal_dish where setemal_id in (1,2,3)
    LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<SetmealDish>();
    lambdaQueryWrapper.in(SetmealDish::getSetmealId, ids);

    //删除关系表中的数据
    setmealDishService.remove(lambdaQueryWrapper);
}

com/itheima/reggie/controller/SetmealController.java

/**
 * 删除套餐功能
 *
 * @param ids
 * @return
 */
@DeleteMapping
public R<String> delete(@RequestParam List<Long> ids) {
    log.info("ids:{}", ids);
    setmealService.removeWithDish(ids);
    return R.success("套餐数据删除成功");
}

当删除套餐成功时,IDEA 工具的控制台的输出的部分 SQL 语句如下所示。

SELECT COUNT( * ) FROM setmeal WHERE (id IN (?) AND status = ?)
DELETE FROM setmeal WHERE id IN ( ? )
DELETE FROM setmeal_dish WHERE (setmeal_id IN (?))

因为是status=0(停售状态)时,才可以删除成功。

但是我们目前又没有做停售功能,为了方便功能测试的进行,我们可以先在数据库中改动信息。


4.其他功能


至此,套餐管理业务开发中,尚未完成的功能有:停售 / 启售批量 / 单个套餐功能修改套餐功能

官方给出的视频里并没有对这些功能的讲解,意在各位自学者自行开发这些功能。

接下来的代码编写则是参考了《瑞吉外卖项目剩余功能补充》这篇博客。


4.1.套餐停售/启售功能


4.1.1.分析


需求分析

  • 在套餐管理列表页面点击停售/启售按钮,可以停售/启售对应的套餐信息。
  • 也可以通过复选框选择多个套餐,点击批量停售/批量启售按钮一次停售/启售多个套餐。

梳理交互过程

在开发代码之前,需要梳理一下停售/启售时前端页面和服务端的交互过程

  • 停售/启售单个套餐时,页面发送 ajax 请求,根据套餐 id停售/启售对应套餐

13.停售单个套餐发送的请求

  • 停售/启售多个套餐时,页面发送 ajax 请求,根据提交的多个套餐 id停售/启售对应套餐

14.停售多个套餐发送的请求
开发停售/启售套餐功能,其实就是在服务端编写代码去处理前端页面发送的这 2 次请求即可。

观察停售/启售单个套餐和批量停售/启售套餐的请求信息可以发现,两种请求的地址和请求方式都是相同的。

不同的是传递的 id 个数,所以在服务端可以提供一个方法来统一处理。


4.1.2.代码开发


com/itheima/reggie/service/SetmealService.java

//停售/启售套餐(单个/批量)
public void status(Integer status, List<Long> ids);

com/itheima/reggie/service/impl/SetmealServiceImpl.java

/**
 * 停售/启售套餐(单个/批量)
 *
 * @param status
 * @param ids
 */
@Transactional
public void status(Integer status, List<Long> ids) {
    //条件构造器
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<Setmeal>();
    //添加条件
    queryWrapper.in(ids != null, Setmeal::getId, ids);

    List<Setmeal> list = this.list(queryWrapper);

    for (Setmeal setmeal : list) {
        if (setmeal != null) {
            setmeal.setStatus(status);
            this.updateById(setmeal);
        }
    }
}

com/itheima/reggie/controller/SetmealController.java

/**
 * 停售/启售套餐(单个/批量)
 *
 * @param status
 * @param ids
 * @return
 */
@PostMapping("/status/{status}")
public R<String> status(@PathVariable("status") Integer status, @RequestParam List<Long> ids) {
    setmealService.status(status, ids);
    return R.success("售卖状态修改成功");
}

以下是IDEA控制台的部分输出语句。

SELECT id,category_id,name,price,status,code,description,image,create_time,update_time,create_user,update_user \
	FROM setmeal WHERE (id IN (?));
UPDATE setmeal SET 
	category_id=?, name=?, price=?, status=?, code=?, description=?, image=?, \
	create_time=?, update_time=?, create_user=?, update_user=? WHERE id=?

4.2.套餐修改功能


4.2.1.分析整理


需求分析

在套餐管理列表页面点击修改按钮,跳转到修改套餐页面,在修改页面回显套餐相关信息并进行修改,最后点击确定按钮完成修改操作。


梳理交互过程

在开发代码之前,需要梳理一下修改套餐时前端页面(add.html)和服务端的交互过程。

  1. 页面发送 ajax 请求,请求服务端获取分类数据,用于套餐分类下拉框中数据展示
  2. 页面发送 ajax 请求,请求服务端,根据 id 查询当前套餐信息,用于套餐信息回显
  3. 页面发送请求,请求服务端进行图片下载,用于页面回显。
  4. 点击保存按钮,页面发送 ajax 请求,将修改后的菜品相关数据以 json 形式提交到服务端

开发修改套餐功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

其中第1步、第3步的操作前面已经完成了。(新增、修改界面本质上用的是同一个界面)


在这里插入图片描述


4.2.2.套餐信息回显


com/itheima/reggie/service/SetmealService.java

//根据 id 查询套餐信息
public SetmealDto getById(Long id);

com/itheima/reggie/service/impl/SetmealServiceImpl.java

/**
 * 根据 id 查询套餐信息
 *
 * @param id
 * @return
 */
@Override
public SetmealDto getById(Long id) {
    //查询套餐的基本信息,从 setmeal 表查询
    Setmeal setmeal = super.getById(id);

    SetmealDto setmealDto = new SetmealDto();
    BeanUtils.copyProperties(setmeal, setmealDto);

    //查询当前套餐对应的菜品。从关联表(setmeal_dish)中查询,
    LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<SetmealDish>();
    queryWrapper.eq(SetmealDish::getSetmealId, setmeal.getId());

    List<SetmealDish> list = setmealDishService.list(queryWrapper);
    setmealDto.setSetmealDishes(list);
    return setmealDto;
}

com/itheima/reggie/controller/SetmealController.java

/**
 * 根据 id 查询套餐信息(包括套餐信息对应的菜品信息)
 *
 * @param id
 * @return
 */
@GetMapping("/{id}")
public R<SetmealDto> get(@PathVariable Long id) {
    SetmealDto setmealDto = setmealService.getById(id);
    return R.success(setmealDto);
}

4.2.3.修改套餐信息


com/itheima/reggie/service/SetmealService.java

//更新套餐信息,同时更新对应的菜品信息
public void updateWithDish(SetmealDto setmealDto);

com/itheima/reggie/service/impl/SetmealServiceImpl.java

/**
 * 更新套餐信息,同时更新对应的菜品信息
 *
 * @param setmealDto
 */
@Override
@Transactional
public void updateWithDish(SetmealDto setmealDto) {
    //更新 setmeal 表的基本信息
    this.updateById(setmealDto);

    //清理当前套餐对应的菜品数据,即 setmeal_dish 表的 delete 操作
    LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<SetmealDish>();
    queryWrapper.eq(SetmealDish::getSetmealId, setmealDto.getId());
    setmealDishService.remove(queryWrapper);

    //添加当前提交的菜品数据,即 setmeal_dish 表的 insert 操作
    List<SetmealDish> mealDishes = setmealDto.getSetmealDishes();

    mealDishes = mealDishes.stream().map((item) -> {
        item.setSetmealId(setmealDto.getId());
        return item;
    }).collect(Collectors.toList());

    setmealDishService.saveBatch(mealDishes);
}

com/itheima/reggie/controller/SetmealController.java

/**
 * 修改套餐信息,同时更新对应的菜品信息
 *
 * @param setmealDto
 * @return
 */
@PutMapping
public R<String> update(@RequestBody SetmealDto setmealDto) {
    log.info(setmealDto.toString());

    if (setmealDto == null) {
        return R.error("请求异常");
    }

    if (setmealDto.getSetmealDishes() == null) {
        return R.error("套餐没有菜品,请添加套餐");
    }

    setmealService.updateWithDish(setmealDto);

    return R.success("修改菜品成功");
}

下一篇学习瑞吉外卖⑥SpringBoot单体项目 https://blog.csdn.net/yanzhaohanwei/article/details/125228024


  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值