瑞吉外卖实战项目全攻略——第四天

该系列将记录一份完整的实战项目的完成过程,该篇属于第四天

案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容

该篇我们将完成以下内容:

  • 文件上传下载
  • 新增菜品
  • 菜品信息分页查询
  • 修改菜品

文件上传下载

由于是第一次接触文件上传下载,我们分为五个小阶段讲解

文件上传介绍

文件上传,也称为upload,是指将本地图片,视频,音频等文件上传到服务器上,可以供其他用户浏览下载的过程

首先我们介绍文件上传对前端的要求:

  • 以POST方法提交数据
  • 采用Multipart格式上传文件
  • 使用input的file空间上传

尽管前端组件库提供了相应的上传组件,但这些组件底层仍旧采用上述要求的格式构造

然后我们介绍文件上传在后端的实现:

  • 通常采用Apache的两个组件:commons-fileupload 和 commons-io

目前我们的Spring框架在Spring-web包下对文件上传进行了封装,简化了服务端代码

我们只需要在Controller中的方法声明一个MultipartFile类型的参数即可接收上传的数据

文件下载介绍

文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程

通过浏览器进行文件下载,通常有两种表现形式:

  • 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
  • 直接在浏览器中打开

通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程

文件上传代码实现

测试用例:

  • 资料中为我们提供了一个文件上传下载的示例页面:upload.html

  • 可以复制在backend/page/demo下,在我们的代码实现过程中进行调试

我们先来思考一下文件上传的逻辑:

  • 我们会获得一个文件File,但这个文件是临时文件,所以我们需要将他储存在电脑中
  • 我们的储存位置需要固定,但是这个文件的名称和后缀名需要重新书写,但还要保证名称随机不会重复,后缀名和原文件相同

接下来我们正式开始文件上传代码的实现:

  1. 书写一个CommonController服务层并构造基本框架:
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

    /**
     * 下载操作
     * @param MultipartFile file 是文件上传的唯一必备代码,代表上传的数据
     * 注意:file只是一个临时文件,当我们的request请求结束时,file也会消失,所以我们需要将它保存起来
     * @return
     */
    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file){
		// 我们可以直接记录日志查看是否收集到file
        log.info(file.toString());
    }

}
  1. 书写内部代码,阐明书写思路:
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

    @Value("${reggie.path}")
    private String BasePath;

    /**
     * 下载操作
     * @param file 注意需要与前端传来的数据名一致
     * @return
     */
    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file){
		// 由于我们的file是临时文件,我们需要将该文件保存在计算机上
        
        try {
            // 这个方法可以转载文件到指定目录
            file.transferTo(new File("D:/hello.jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 注意:我们需要返回文件名,因为我们还需要让图片回显,回显就需要再次传入文件名来查询文件
        return Result.success(fileName);
    }

}
  1. 在yaml配置文件中书写全局变量,使存放路径固定:
# 我们定义了一个reggie.path的参数来存放我们存放文件的地址
reggie:
  path: E:\imgs\
  1. 回到主函数中书写代码:
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

/*
目前我们的文件存放已经有了固定的位置
但是我们的文件名以及后缀文件名还没有获得,我们将把它们变为动态的
*/

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

    // 定义主路径
    @Value("${reggie.path}")
    private String BasePath;

    /**
     * 下载操作
     * @param file 注意需要与前端传来的数据名一致
     * @return
     */
    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file){
        // 注意:file只是一个临时文件,当我们的request请求结束时,file也会消失,所以我们需要将它保存起来

        // 这个方法可以获得文件的原名称,但不推荐设置为文件名保存(因为可能出现重复名称导致文件覆盖)
        String originalFilename = file.getOriginalFilename();

        // 将原始文件的后缀截取下来
        String substring = originalFilename.substring(originalFilename.lastIndexOf("."));

        // UUID生成随机名称,文件名设置为 UUID随机值+源文件后缀
        String fileName = UUID.randomUUID().toString() + substring;

        // 判断文件夹是否存在,若不存在需创建一个
        File dir = new File(BasePath);

        if (!dir.exists()){
            dir.mkdirs();
        }

        // 这个方法可以转载文件到指定目录
        try {
            file.transferTo(new File(BasePath + fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Result.success(fileName);
    }
}

文件下载代码实现

同样我们来思考一下文件下载的逻辑:

  • 首先我们根据前端传来的名字与我们设置的固定路径得到文件
  • 然后我们直接将文件的内容采用输出流的形式输出到前端即可

我们的文件下载同时在CommonController中实现:

  1. 书写主体框架
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

    @Value("${reggie.path}")
    private String BasePath;

    /**
     *
     * @param name
     * @param response
     * @return
     */
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response){
		// 我们将在里面实现,读取文件,再返回文件的操作
    }
}
  1. 实现内部逻辑
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

    @Value("${reggie.path}")
    private String BasePath;

    /**
     * 文件下载
     * @param name
     * @param response
     * @return
     */
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response){

        try {
            // 输入流获得数据
            FileInputStream fileInputStream = new FileInputStream(new File(BasePath + name));

            // 输出流写出数据
            ServletOutputStream outputStream = response.getOutputStream();

            // 设置文件类型(可设可不设)
            response.setContentType("image/jpeg");

            // 转载数据
            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = fileInputStream.read(bytes)) != -1){
                outputStream.write(bytes,0,len);
                outputStream.flush();
            }

            // 关闭数据
            fileInputStream.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

实际测试

我们打开之前转载过来的upload.html页面,传进照片后照片显示在页面中即为成功

新增菜品

我们的功能开发通常分为三部分

需求分析

当我们点击页面的菜品的新添时,会弹出页面,我们根据这个页面进行分析:
瑞吉外卖实战项目全攻略——第四天-LMLPHP

我们需要注意,这里还有一个菜品分类的下拉框,其中菜品分类的数据是通过请求到后台最后到数据库查询所得到的:

瑞吉外卖实战项目全攻略——第四天-LMLPHP

此外我们点击保存时,还会将一个菜品的相关信息请求返回后台:

瑞吉外卖实战项目全攻略——第四天-LMLPHP

我们需要注意的是:这里返回的并不仅仅只有菜品Dish的数据表,还包括了调料Dish Flavor的数据表

所以我们得到数据时不能采用Dish来获得数据

这里我们提出一个新的概念:

  • DTO(Data Transfer Object)

这是一种设计模式之间传输数据的软件应用系统,它用于储存一些不属于同一个数据库中的一些信息

我们会新创一种DishDto实体类来继承Dish实体类并且新添一些关于DishFlavor的属性或方法来接收整个数据

在最后我们简单查看一下Dish和DishFlavor的数据表:

瑞吉外卖实战项目全攻略——第四天-LMLPHP

其中category_id表示所属分类的id

瑞吉外卖实战项目全攻略——第四天-LMLPHP

其中dish_id表示该调味的菜的id

代码实现

首先我们来实现一个简单的功能,获得菜品分类并返回前端页面:

package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {

    @Autowired
    private CategoryServiceImpl categoryService;

    /**
    * 返回所有分类名称
    */   
    @GetMapping("/list")
    public Result<List<Category>> list(Category category){

        // 创造一个LambdaQueryWrapper来判断Type=1并设置排序顺序
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
        queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);

        // 查询信息
        List<Category> list = categoryService.list(queryWrapper);

        // 返回信息
        return Result.success(list);
    }
}

接下来我们来实现存储双数据表到数据库的操作:

  1. 基本准备
# 首先我们来完成一些基本准备(我们的DishFlavor的操作也会放在DishController中实现)

实体类DishFlavor
数据层DishFlavorMapper
业务层接口DishFlavorService
业务层DishFlavorServiceImpl
服务层DishController
  1. 创建实体类DishDto(资料已提供)
// 我们通常单独创建一个包dto来装在DTO类

package com.qiuluo.reggie.dto;

import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;

@Data
public class DishDto extends Dish {

    // 在里面设置了List<DishFlavor> flavors 来装载 flavors
    private List<DishFlavor> flavors = new ArrayList<>();

    // categoryName 表示属于的分类名
    private String categoryName;

    private Integer copies;
}
  1. 新方法业务层接口实现
package com.qiuluo.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;

public interface DishService extends IService<Dish> {

    // 由于当前的默认方法无法满足我们的需求,我们创建新的方法来实现该操作
    
    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavor
    public void saveWithFlavor(DishDto dishDto);
}
  1. 新方法业务层实现
package com.qiuluo.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.mapper.DishMapper;
import com.qiuluo.reggie.service.DishService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

    // 调入dishFlavor的业务层实现类
    @Autowired
    private DishFlavorServiceImpl dishFlavorService;

    public void saveWithFlavor(DishDto dishDto){

        // 1. 将菜品数据导入
        this.save(dishDto);

        // 2. 将Flavor导入(注意:Flavor传入时没有传入dishID,需要我们手动设置)
        List<DishFlavor> flavors = dishDto.getFlavors();
        for (DishFlavor flavor:flavors) {
            flavor.setDishId(dishDto.getId());
        }

        dishFlavorService.saveBatch(flavors);

    }

}
  1. 服务层实现
package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

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

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishServiceImpl dishService;

    /**
     * 新增菜品
     * @param dishDto
     * @return
     */
    @PostMapping
    public Result<String> save(@RequestBody DishDto dishDto){

        dishService.saveWithFlavor(dishDto);

        return Result.success("新创成功");
    }

}

实际测试

点击打开新建菜品,填写数据点击后数据库出现相关菜品即可

菜品信息分页查询

我们的功能开发通常分为三部分

需求分析

系统中菜品很多时,我们需要采用分页查询,使菜品多批出现防止拥挤导致页面阅读不方便

这里我们先给出完成操作后的图片:

瑞吉外卖实战项目全攻略——第四天-LMLPHP

我们需要注意的是这里的菜品分类直接出了分类的名称

但是我们的Dish中只给出了菜品分类的id,所以我们还需要借用id去查出所属分类的名称并重新赋值

代码实现

首先我们来完成简单的分页查询操作:

package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

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

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishServiceImpl dishService;

    @Autowired
    private CategoryServiceImpl categoryService;

    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public Result<Page> page(int page,int pageSize,String name){

        // 构造基本Page
        Page<Dish> pageImpl = new Page<>(page,pageSize);

        // 进行查询
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(name != null,Dish::getName,name);
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        // 查询赋值,此时pageImpl里有值
        dishService.page(pageImpl,queryWrapper);

        return Result.success(pageImpl);

    }

}

此时我们出来的页面中是无法查看到分类所属的:

瑞吉外卖实战项目全攻略——第四天-LMLPHP

所以我们需要设置包含有菜品分类名称的实体类作为Page的实现类参数才可以将菜品分类的名称传递到前端

我们只需要到前端代码中查看就可以注意到,商品分类这行上的数据属性名称为categoryName,所以我们采用DishDto来完成操作

我们将对代码进行修改:

package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

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

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishServiceImpl dishService;

    @Autowired
    private CategoryServiceImpl categoryService;

    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public Result<Page> page(int page,int pageSize,String name){

        // 构造基本Page
        Page<Dish> pageImpl = new Page<>(page,pageSize);

        // 因为返回类型中多了一个categoryName,我们还需要构造一个DishDto类型的Page
        Page<DishDto> dishDtoPage = new Page<>();

        // 进行查询
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(name != null,Dish::getName,name);
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        // 查询赋值,此时pageImpl里有值
        dishService.page(pageImpl,queryWrapper);

        // 但是我们需要返回DishDto类型的Page,我们将pageImpl的值赋值到dishDtoPage中(不要赋值records,这个值是数据,我们需要单独处理)
        // 我们借助工具类实现
        BeanUtils.copyProperties(pageImpl,dishDtoPage,"records");

        // 然后我们来处理dishDtoPage中的records值,我们首先将pageImpl的records提取出来
        List<Dish> records = pageImpl.getRecords();

        // 将该值除了CategoryName全都赋值给dishDtoPage的records(我们这里使用stream流进行局内单个赋值,采用foreach方法也相同)
        List<DishDto> dishDtoList = records.stream().map((item) -> {
            // 创建一个新dishDto作为返回实体
            DishDto dishDto = new DishDto();

            // 将正常属性赋值进去
            BeanUtils.copyProperties(item,dishDto);

            // 将CategoryName复制进去
            Long categoryId = item.getCategoryId();
            Category category = categoryService.getById(categoryId);

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

            return dishDto;
        }).collect(Collectors.toList());

        // 完成dishDtoPage的results的内容封装
        dishDtoPage.setRecords(dishDtoList);

        return Result.success(dishDtoPage);

    }

}

实际测试

当我们打开页面时,所有数据均出现在页面即为代码实现成功

修改菜品

我们的功能开发通常分为三部分

需求分析

首先我们点击菜品后面的修改来查看修改页面:
瑞吉外卖实战项目全攻略——第四天-LMLPHP

首先我们会发现我们的菜品数据会展现在页面中,这是因为在打开时页面就发送了一个请求来获得该菜品的相关信息并回显:
瑞吉外卖实战项目全攻略——第四天-LMLPHP

然后我们会发现菜品分类下拉框的数据又出来了,这里我们在前面的新填菜品中已经实现了

最后我们只需要点击提交查看相关信息即可:
瑞吉外卖实战项目全攻略——第四天-LMLPHP

代码实现

我们这次需要实现两个流程

首先我们来实现根据id返回数据的代码实现:

  1. 业务层接口实现
package com.qiuluo.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;

public interface DishService extends IService<Dish> {
    
    //查询菜品,并查询对应口味
    public DishDto getByIdWithFlavors(Long id);

}
  1. 业务层实现
package com.qiuluo.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.mapper.DishMapper;
import com.qiuluo.reggie.service.DishService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

    // 调入dishFlavor的业务层实现类
    @Autowired
    private DishFlavorServiceImpl dishFlavorService;
    
    /**
     * 查询菜品,并查询对应口味
     * @param id
     * @return
     */
    public DishDto getByIdWithFlavors(Long id){

        // 创造返回对象
        DishDto dishDto = new DishDto();

        // 首先根据id获得菜品信息
        Dish dish = this.getById(id);

        // 根据id获得调料信息
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DishFlavor::getDishId,id);

        List<DishFlavor> list = dishFlavorService.list(queryWrapper);


        // 将数据传入
        BeanUtils.copyProperties(dish,dishDto);
        dishDto.setFlavors(list);

        return dishDto;
    }

}
  1. 服务层实现
package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

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

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishServiceImpl dishService;

    /**
     * 得到数据
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result<DishDto> get(@PathVariable Long id){

        DishDto dishDto = dishService.getByIdWithFlavors(id);

        return Result.success(dishDto);
    }

}

接下来我们来实现修改操作:

  1. 业务层接口实现
package com.qiuluo.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;

public interface DishService extends IService<Dish> {

    // 修改菜品
    public void updateWithFlavor(DishDto dishDto);
}
  1. 业务层实现
package com.qiuluo.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.mapper.DishMapper;
import com.qiuluo.reggie.service.DishService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

    // 调入dishFlavor的业务层实现类
    @Autowired
    private DishFlavorServiceImpl dishFlavorService;

    // 修改菜品
    public void updateWithFlavor(DishDto dishDto){

        // Dish修改
        this.updateById(dishDto);

        // Flavor修改(我们先全部删除,再全部重新添加)

        // 删除操作
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());

        dishFlavorService.remove(queryWrapper);


        // 添加操作
        List<DishFlavor> flavors = dishDto.getFlavors();

        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishDto.getId());
            return item;
        }).collect(Collectors.toList());

    }

}
  1. 服务层实现
package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

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

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishServiceImpl dishService;

    @Autowired
    private CategoryServiceImpl categoryService;

    /**
     * 修改数据
     * @param
     * @return
     */
    @PutMapping
    public Result<String> update(@RequestBody DishDto dishDto){

        dishService.updateWithFlavor(dishDto);

        return Result.success("修改完成");

    }

}

实际测试

回到菜品页面,修改相关信息点击修改,若修改成功,代码即为成功

批量操作

我们的功能开发通常分为三部分

需求分析

该节属于视频未提及的简单操作,只需要调用简单的业务层函数处理即可

我们需要完成批量删除,批量启售,批量停售,我们点击相关菜品后,依次点击方法,F12查看url以及传递的参数:

# 批量删除
url:http://localhost:8080/dish?ids=1583025958761824258
Method:DELETE

# 批量启售
url:http://localhost:8080/dish/status/1?ids=1413384757047271425,1413385247889891330
Method:POST

# 批量停售
url:http://localhost:8080/dish/status/0?ids=1413384757047271425,1413385247889891330
Method:POST

代码实现

我们直接给出代码展示:

package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

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

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishServiceImpl dishService;

    /**
     * 批量删除
     * @param ids
     * @return
     */
    @DeleteMapping
    public Result<String> deleteByIds(Long[] ids){

        for (Long id:ids
             ) {
            dishService.removeById(id);
        }

        return Result.success("删除成功");
    }

     /**
     * 批量启售
     * @param ids
     * @return
     */

    @PostMapping("/status/1")
    public Result<String> openStatus(Long[] ids) {

        for (Long id : ids
        ) {
            Dish dish = dishService.getById(id);
            dish.setStatus(1);
            dishService.updateById(dish);
        }

        return Result.success("修改成功");
    }

    
     /**
     * 批量停售
     * @param ids
     * @return
     */
    @PostMapping("/status/0")
    public Result<String> closeStatus(Long[] ids) {

        for (Long id : ids
        ) {
            Dish dish = dishService.getById(id);
            dish.setStatus(0);
            dishService.updateById(dish);
        }

        return Result.success("修改成功");
    }
}

实际测试

返回菜品页面,点击若干菜品,进行三个方法的测试,实现即可

易错点

在这里我们会点出该项目目前容易出错的位置

DTO实体类

我们在这里重新强调一下DTO:

  • 数据传输对象(DTO)(Data Transfer Object),是一种设计模式之间传输数据的软件应用系统。

DTO的作用我们在实例中已经很清楚了:

  • 当我们目前的实体类不足以接收请求的数据或者请求数据包含了多个数据表的属性时使用DTO

DTO的原理实际上很简单:

  • DTO只是在继承原本实体类的基础上新添一些所需要的属性来接收数据

总而言之DTO在我们的实际开发中是很有用的哦~

业务层方法实现

我们在前面的文章中基本都是借助SpringBoot和MyBaits所提供的方法来进行开发

但在实际业务中,我们遇到的查询语法或查询条件或者需要操作的步骤不是简单的方法所能实现的

这时我们就需要采用原始的方法重新进行业务层的方法实现:

  1. 业务层接口声明
首先在业务层接口声明该方法,在这时要想要返回的类型,想要需要什么参数
  1. 业务层实现
这里主要负责方法的实现,我们要根据自己的逻辑思路来一步一步完成方法
  1. 服务层实现
到了这层基本就是直接调用业务层实现的方法即可

结束语

该篇内容到这里就结束了,希望能为你带来帮助~

附录

该文章属于学习内容,具体参考B站黑马程序员的Java项目实战《瑞吉外卖》

这里附上视频链接:业务开发Day4-01-本章内容介绍_哔哩哔哩_bilibili

10-22 12:10