黑马程序员Java项目实战《瑞吉外卖》 笔记 Day4:视频P49-P68

黑马程序员Java项目实战《瑞吉外卖》 笔记

本课程以当前热门的外卖点餐为业务基础,业务真实、实用、广泛。基于流行的Spring Boot、mybatis
plus等技术框架进行开发,带领学员体验真实项目开发流程、需求分析过程和代码实现过程。学完本课程能够收获:锻炼需求分析能力、编码能力、bug调试能力,增长开发经验。

  • 链接:https://www.bilibili.com/video/BV13a411q753

20230321 Day4:视频P49-P68

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

文件上传下载:

文件上传介绍:

文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。文件上传时,对页面的form表单有如下要求:

  • method=“post” 采用post方式提交数据
  • enctype=“multipart/form-data” 采用multipart格式上传文件
  • type=“file” 使用input的file控件上传

在controller包中创建CommonController类,并创建upload方法

package com.reggie.controller;

import com.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * 文件上传下载
 */
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file) {

        log.info("上传文件成功,内容为:{}", file.toString());
        return null;
    }
}

调用file的方法,将临时文件转存到指定目录

try{
        //将文件转存到指定位置
        file.transferTo(new File("D:\\hello.jpg"));
        }catch(IOException e){
        throw new RuntimeException(e);
        }
        log.info("上传文件成功,内容为:{}",file.toString());
        return null;

定义一个变量basePath存储基本路径,并在application.yml中配置初始值

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

使用uuid重新生成文件名,防止文件名重复造成文件覆盖,并通过字符串截取获取源文件拓展名,得到新文件名

String originalFilename=file.getOriginalFilename();
        String suffix=originalFilename.substring(originalFilename.lastIndexOf("."));
//使用uuid重新生成文件名,防止文件名重复造成文件覆盖
        String fileName=UUID.randomUUID().toString()+suffix;
        String fileName=UUID.randomUUID().toString()+suffix;

判断选择的路径是否存在,若不存在则创建此目录

File dir=new File(basePath);
        if(!dir.exists()){
        dir.mkdirs();
        }

upload方法完整代码如下:

@Value("${reggie.path}")
private String basePath;
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
        String originalFilename=file.getOriginalFilename();
        String suffix=originalFilename.substring(originalFilename.lastIndexOf("."));
        //使用uuid重新生成文件名,防止文件名重复造成文件覆盖
        String fileName=UUID.randomUUID().toString()+suffix;
        //创建一个目录对象
        File dir=new File(basePath);
        if(!dir.exists()){
        dir.mkdirs();
        }
        try{
        //将文件转存到指定位置
        file.transferTo(new File(basePath+fileName));
        }catch(IOException e){
        throw new RuntimeException(e);
        }
        log.info("上传文件成功,内容为:{}",file.toString());
        return R.success(fileName);
        }

文件下载介绍:

文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,通常有两种表现形式:

  • 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
  • 直接在浏览器中打开
    通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。

创建输出流和输入流,分别用于下载图片和读取图片信息

//输入流,通过输入流读取文件内容
FileInputStream fileInputStream=new FileInputStream(new File(basePath+name));
//输出流,通过输出流将文件写回浏览器,在浏览器展示图片
        ServletOutputStream outputStream=response.getOutputStream();

创建while循环,当读取结束后向客户端返回图片数组bytes,并关闭资源

byte[]bytes=new byte[1024];
        response.setContentType("image/jpeg");
        int len=0;
        while((len=fileInputStream.read(bytes))!=-1){
        outputStream.write(bytes,0,len);
        outputStream.flush();
        }
        fileInputStream.close();
        outputStream.close();

文件下载完整代码如下:

@GetMapping("/download")
public void download(String name,HttpServletResponse response){
        try{
        //输入流,通过输入流读取文件内容
        FileInputStream fileInputStream=new FileInputStream(new File(basePath+name));
        //输出流,通过输出流将文件写回浏览器,在浏览器展示图片
        ServletOutputStream outputStream=response.getOutputStream();
        byte[]bytes=new byte[1024];
        response.setContentType("image/jpeg");
        int len=0;
        while((len=fileInputStream.read(bytes))!=-1){
        outputStream.write(bytes,0,len);
        outputStream.flush();
        }
        //关闭资源
        fileInputStream.close();
        outputStream.close();
        log.info("文件下载成功,路径为:{}",basePath+name);
        }catch(IOException e){
        throw new RuntimeException(e);
        }
        }

新增菜品:

需求分析

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

数据模型

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。
所以在新增菜品时,涉及到两个表:

  • dish 菜品表
  • dish_flavor 菜品口味表

代码开发-准备工作

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

实体类DishFlavor

package com.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 菜品口味
 */
@Data
public class DishFlavor implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //菜品id
    private Long dishId;


    //口味名称
    private String name;


    //口味数据list
    private String value;


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

}

Mapper接口DishFlavorMapper

package com.reggie.mapper;

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

@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {

}

业务层接▣DishFlavorService

package com.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.reggie.entity.DishFlavor;

public interface DishFlavorService extends IService<DishFlavor> {

}

业务层实现类DishFlavorServicelmpl

package com.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.reggie.entity.DishFlavor;
import com.reggie.mapper.DishFlavorMapper;
import com.reggie.service.DishFlavorService;
import com.reggie.service.DishService;
import org.springframework.stereotype.Service;

@Service
public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor> implements DishFlavorService {

}

控制层DishController

package com.reggie.controller;

import com.reggie.service.DishFlavorService;
import com.reggie.service.DishService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 菜品管理
 */
@RestController
@RequestMapping("/dish")
public class DishController {
    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;
}

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

1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中
2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
3、页面发送请求进行图片下载,将上传的图片进行回显
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

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

在CategoryController中创建list方法,用于根据条件查询分类数据,在其中创建条件构造器,并添加查询和排序条件

@GetMapping("/list")
public R<List<Category>>list(Category category){
        //条件构造器
        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 R.success(list);
        }

导入DishDto(位置:资料/dto),用于封装页面提交的数据

package com.reggie.DTO;

import com.reggie.entity.Dish;
import com.reggie.entity.DishFlavor;
import lombok.Data;

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

@Data
public class DishDto extends Dish {

    private List<DishFlavor> flavors = new ArrayList<>();

    private String categoryName;

    private Integer copies;
}

在DishController创建save方法用于新增菜品信息,并添加@PostMapping注解

@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
        log.info("获取到菜品,内容为:{}",dishDto.toString());
        return null;
        }

在DishService创建saveWithFlavor方法

package com.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.reggie.DTO.DishDto;
import com.reggie.entity.Dish;

public interface DishService extends IService<Dish> {
    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:Dish、dish_Flavor
    public void saveWithFlavor(DishDto dishDto);
}

在DishServiceImpl中实现方法,调用save方法保存菜品基本信息

@Override
public void saveWithFlavor(DishDto dishDto){
        //保存菜品的基本信息到菜品表dish
        this.save(dishDto);
        }

通过stream流为dishid赋值,最后调用dishFlavorService的saveBatch方法保存flavors

Long dishId=dishDto.getId();
        List<DishFlavor> flavors=dishDto.getFlavors();
        flavors.stream().map((item)->{
        item.setDishId(dishId);
        return item;
        }).collect(Collectors.toList());
//保存菜品口味数据到口味表dish_Flavor
        dishFlavorService.saveBatch(flavors);

为ReggieApplication类添加@EnableAutoConfiguration注解,并为saveWithFlavor方法添加事务处理@Transactional

@Override
@Transactional
public void saveWithFlavor(DishDto dishDto){
        //保存菜品的基本信息到菜品表dish
        this.save(dishDto);

        Long dishId=dishDto.getId();
        List<DishFlavor> flavors=dishDto.getFlavors();
        flavors.stream().map((item)->{
        item.setDishId(dishId);
        return item;
        }).collect(Collectors.toList());
        //保存菜品口味数据到口味表dish_Flavor
        dishFlavorService.saveBatch(flavors);
        }

在DishController中调用刚刚创建的saveWithFlavor方法保存新增菜品

@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
        log.info("获取到菜品,内容为:{}",dishDto.toString());
        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
        }

菜品信息分页查询:

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

1、页面(backend/page/food/ist.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
2、页面发送请求,请求服务端进行图片下载,用于页面图片展示

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

在DishController中创建page方法用于获取分页数据,仿照之前的分页方法,添加构造器

@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
        log.info("接收到查询请求,page={},pageSize={},name={}",page,pageSize,name);
        //构造分页构造器
        Page<Dish> pageinfo=new Page<>(page,pageSize);
        //构造条件构造器
        LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper();
        //添加过滤条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Dish::getUpdateTime);
        //执行查询
        dishService.page(pageinfo,queryWrapper);
        return R.success(pageinfo);
        }

向DishController中注入CategoryService

@Autowired
private CategoryService categoryService;

创建一个DishDto泛型的list,通过调用BeanUtils的copyProperties方法拷贝pageinfo的属性,并使用stream流,向list中添加分类信息,完整代码如下:

@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
     log.info("接收到查询请求,page={},pageSize={},name={}",page,pageSize,name);
     //构造分页构造器
     Page<Dish> pageinfo=new Page<>(page,pageSize);
     Page<DishDto> dishDtoPage=new Page<>(page,pageSize);
     //构造条件构造器
     LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper();
     //添加过滤条件
     queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
     //添加排序条件
     queryWrapper.orderByDesc(Dish::getUpdateTime);
     //执行查询
     dishService.page(pageinfo,queryWrapper);
     BeanUtils.copyProperties(pageinfo,dishDtoPage,"records");

     List<Dish> records=pageinfo.getRecords();
     List<DishDto> list=null;

     list=records.stream().map((item)->{
        DishDto dishDto=new DishDto();
        Long categoryId=item.getCategoryId();
        Category category=categoryService.getById(categoryId);
        String categoryName=category.getName();
        BeanUtils.copyProperties(item,dishDto);
        dishDto.setCategoryName(categoryName);
        return dishDto;

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

     dishDtoPage.setRecords(list);

     return R.success(dishDtoPage);
     }

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

1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示
2、页面发送jax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
3、页面发送请求,请求服务端进行图片下载,用于页图片回显
4、点击保存按钮,页面发送ajx请求,将修改后的菜品相关数据以json形式提交到服务端
开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

在DishService中创建方法getByIdWithFlavor,并DishServiceImpl在中实现方法
通过LambdaQueryWrapper查找到对应口味,并通过BeanUtils的copyProperties方法拷贝到dishDto对象中,最后返回dishDto

@Override
 public DishDto getByIdWithFlavor(Long id) {
     DishDto dishDto = new DishDto();
     Dish dish = this.getById(id);
     BeanUtils.copyProperties(dish,dishDto);

     LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
     queryWrapper.eq(DishFlavor::getDishId,dish.getId());
     List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
     dishDto.setFlavors(flavors);

     return dishDto;
 }

在DishService中创建方法updateByIdWithFlavor,并DishServiceImpl在中实现方法

this.updateById(dishDto);

清理当前来品对应口味数据 dish flavor表的delete操作

LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
dishFlavorService.remove(queryWrapper);

添加当前提交过来的口味数据 dish flavor表的insert操作

Long dishId = dishDto.getId();
List<DishFlavor> flavors = dishDto.getFlavors();
flavors.stream().map((item) ->{
   item.setDishId(dishId);
   return item;
}).collect(Collectors.toList());

保存菜品口味数据到口味表dish_Flavor,完整代码如下:

@Override
public void updateByIdWithFlavor(DishDto dishDto) {
   //保存菜品的基本信息到菜品表dish
   this.updateById(dishDto);
   //清理当前来品对应口味数据 dish flavor表的delete操作
   LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
   queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
   dishFlavorService.remove(queryWrapper);
   //添加当前提交过来的口味数据 dish flavor表的insert操作
   Long dishId = dishDto.getId();
   List<DishFlavor> flavors = dishDto.getFlavors();
   flavors.stream().map((item) ->{
      item.setDishId(dishId);
      return item;
   }).collect(Collectors.toList());
   //保存菜品口味数据到口味表dish_Flavor
   dishFlavorService.saveBatch(flavors);
}

你可能感兴趣的:(java,mybatis,spring,boot,spring,maven)