• 【SpringBoot项目】一文掌握文件上传和下载【业务开发day04】


    🌕博客x主页:己不由心王道长🌕!
    🌎文章说明:SpringBoot项目-瑞吉外卖【day04】业务开发🌎
    ✅系列专栏:SpringBoot项目
    🌴本篇内容:对黑马的瑞吉外卖项目的day04进行笔记和项目实现🌴
    ☕️每日一语:这个世界本来就不完美,如果我们再不接受不完美的自己,那我们要怎么活。☕️
    🚩 交流社区:己不由心王道长(优质编程社区)

    前言

    本章是对黑马瑞吉外卖day04的一个学习和代码实现,本章的重点在于文件的上传和下载。由于上传和下载十分重要,所以这是需要务必掌握的,特别是注意上传时的要求。至于为什么上传和下载那么重要,其实我们生活中也可以体会,比如我们更改我们的微信头像,其实就是把图片传给服务端,然后再下载回显到我们的用户端。

    文件上传下载

    文件上传介绍

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

    文件上传时,是以表单进行提交的,而文件上传时对页面的form表单有如下要求:

    在这里插入图片描述
    method是:post方式、enctype是:multipart/form-data,type是:file

    举例:
    在这里插入图片描述
    目前一些前端组件库已经提供了相应的上传组件,但是底层原理还是基于form表单的文件上传。例如ElementUI提供的upload上传组件:
    在这里插入图片描述

    服务端:
    服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:

    commons-fileupload
    commons-io

    这里spring框架已经在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件。

    文件下载介绍

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

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

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

    文件上传代码实现

    在这里插入图片描述
    校验部分是一个上传成功处理器,在那里会调用一个下载的controller去把刚才上传的文件下载并回显到我们的页面。

    注意上面的看这个,我们去看看它。
    在这里插入图片描述
    上面的分析看看就好,我们现在已经知道路径和上传的条件了,开始编写对应的代码实现它:注意这里前端传的参数是file,后端接收的时候名称要一样,这是无注解参数处理

    这里导入一个专门做上传下载的页面:
    在这里插入图片描述
    代码实现:

    package com.example.controller;
    
    /**
     * @author 不止于梦想
     * @date 2022/11/18 14:47
     */
    
    import com.example.commons.R;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    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 java.io.File;
    import java.io.IOException;
    import java.util.UUID;
    
    /**
     * 实现文件上传与下载的公共方法
     */
    @RestController
    @RequestMapping("/common")
    @Slf4j
    public class CommonsController {
        //注入属性文件存放上传文件所在路径
        @Value("${reggie.path}")
        private String basePath;
        @PostMapping("/upload")
        public R<String> upload(MultipartFile file)  {
            //file是一个零时文件,需要转存到指定文件目录,否则本次请求结束后将自动删除
            //获取文件原始名称
            String originalFilename = file.getOriginalFilename();
            //获取后缀格式
            String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
            log.info(file.toString());
            //使用uuid重新生成文件名,防止文件重名覆盖。
            String fileName = UUID.randomUUID()+suffix;
            //创建一个目录对象,
            File dir =new File(basePath);
            //判断目录是否存在
            if(!dir.exists()){//如果目录不存在
                dir.mkdir();//创建一个目录
            }
            //将临时文件转到指定目录
            try {
                file.transferTo(new File(basePath+fileName));
            } catch (IOException e) {
                e.printStackTrace();
            }
            //成功返回文件名
            return R.success(fileName);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    测试:
    我选择一张坤坤的图片进行上传:
    在这里插入图片描述
    目录在D盘的dream下,现在这个目录还没有,我们点击上传:
    在这里插入图片描述
    为验证结果成功,我们输出日志:
    在这里插入图片描述
    在这里插入图片描述

    文件下载代码实现

    在上面我们说到,当我们上传文件之后,把文件名字返回给前台,前台会调用下载然后回显图片。
    在这里插入图片描述
    方式是get,因为是读数据。参数是文件名name。

    这里先写一个简单的路径,看看是不是上传以后马上就发了下载的请求然后回显:
    在这里插入图片描述
    当我上传之后,打点debug显示前端会自动来调用下载回显的数据,我们现在只需要关心如下下载并且回显数据即可。

    @GetMapping("/download")
        public void download(String name, HttpServletResponse response) {//这里的response是因为要回显数据
            //打印访问日志信息
            log.info("name:{}", name, response.toString());
            try {
                //读图片文件,basePath是我们原本的保存位置,name也是上传时保存的名字
                FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
                //输出流,通过输出流将文件写回浏览器
                ServletOutputStream outputStream = response.getOutputStream();
                //设置想要类型
                response.setContentType("image/jpeg");
                //通过byte输出,边读边输出
                int len = 0;
                byte[] bytes = new byte[1024];
                while ((len = fileInputStream.read(bytes)) != -1) {//这里读
                    //这里写,设置写的长度,不然会重复写
                    outputStream.write(bytes, 0, len);
                    //这里刷新
                    outputStream.flush();
                }
                //关闭资源
                outputStream.close();
                fileInputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    测试:
    在这里插入图片描述
    成功回显到前端页面。

    新增菜品

    需求分析

    后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。
    在这里插入图片描述
    当我们点击新建菜品时,会向后台发送一个请求,如下图:
    在这里插入图片描述
    上面这个请求查询的是type=1的菜品分类,我们在前面的时候写过这个,type在这里表示套餐和菜品。如下图,查询type=1,即菜品种类,然后制成一个下拉列表。路径是category
    在这里插入图片描述
    这是第一个要写的方法。

    当我们把上面的信息填好之后,需要添加图片,图片资源就是我们上面编写的文件上传下载功能,这里可以直接用,因为路径是一样的。

    当我们点击保存之后,才是挑战的开始,这里并不是直接把数据存放在一个表之中,到底怎么回事,我们去瞧瞧前端代码
    在这里插入图片描述
    首先是flavor:口味,是一类一类的,而且我们前面也没有flavor的数据类型,怎么接收?
    这里的一个难点就是,这一个数据提交得分别存入两个表中,一个是dish_flavor,一个是dish。

    数据模型

    我们看看数据模型:
    数据模型:

    新增菜品,其实就是将新增页面录入的菜品信息插入到dih表,如果添加了口味做法,还需要向dihflaVOr表插入数据.所以在新增乘品时,涉及到两个表:
    dish 菜品表
    dish flavo r菜品口味表
    在这里插入图片描述

    代码开发

    我们首先先把菜品分类下拉集合编写:
    菜品分类:

    @GetMapping("/list")
        public R<List<Category>> list(Category category){
            log.info(category.getType().toString());
            //条件构造器
            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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试:
    在这里插入图片描述

    到这里我们还没有完成任务,当我们填完表单提交以后,我们需要在后台接收数据和分别存入数据

    这里要接收数据,我们得学习一个新的知识点,DTO(data transfer object),即数据传输对象,用来把不同数据进行一个组合成为一个新的对象然后接收数据的方法。
    这里直接导入官方给的dto
    在这里插入图片描述
    这里爆红,我们上面说了口味类型,这里还没有创建,也是直接导入官方给的数据。
    导入了一个entity对象,一般就需要创建其对应三层架构的,这里也不例外。这样更有层次和结构性。

    我们这里看看前端代码给我们传入了什么数据:
    在这里插入图片描述
    这些数据类型,接收的时候得使用数据传输对象作为参数接收,而且格式是json格式;

    在这里插入图片描述
    路径方式:
    编写简单路径,查看是否能够接收到数据
    在这里插入图片描述

    在这里插入图片描述
    数据可以到达,这个时候就要编写代码实现了。
    我们首先要考虑的是,因为这个数据涉及到多表,Mybatisplus是没有帮我们写好的,这个时候我们应该在业务层定义一个方法,通过业务层的调用分解,把数据存入不同的表中,对controller提供一个方法即可。

    package com.example.controller;
    
    import com.example.commons.R;
    import com.example.dto.DishDto;
    import com.example.service.DishService;
    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;
    
    /**
     * @author 不止于梦想
     * @date 2022/11/18 19:07
     */
    @RestController
    @RequestMapping("/dish")
    @Slf4j
    public class DishController {
        @Autowired
        DishService dishService;
        @PostMapping
        public R<String> save(@RequestBody DishDto dishDto){
            log.info("dishDto:{}",dishDto);
            dishService.saveWithFlavor(dishDto);
            return R.success("添加成功");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    package com.example.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.example.dto.DishDto;
    import com.example.entity.Dish;
    import com.example.entity.DishFlavor;
    import com.example.mapper.DishMapper;
    import com.example.service.DishFlavorService;
    import com.example.service.DishService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Iterator;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @author 不止于梦想
     * @date 2022/11/17 20:31
     */
    @Service
    public class DishServiceImpl extends ServiceImpl<DishMapper,Dish> implements DishService {
       @Autowired
        DishFlavorService dishFlavorService;
        @Override
        /**
         * 保存信息到dish表和dishflavor表
         */
        @Transactional
        public void saveWithFlavor(DishDto dishDto) {
            //把数据存入dish表中
            this.save(dishDto);
            //菜品id
            Long dishId = dishDto.getId();
            //菜品口味
            List<DishFlavor> flavors = dishDto.getFlavors();
            //方式一
    //        flavors = flavors.stream().map((item) -> {
    //            item.setDishId(dishId);
    //            return item;
    //        }).collect(Collectors.toList());
    //方式二
            for (int i = 0; i < flavors.toArray().length; i++) {
                flavors.get(0).setDishId(dishId);
            }
    
            //保存菜品口味数据到菜品口味表dish_flavor
            dishFlavorService.saveBatch(flavors);
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    这里提供了两个处理集合的方式,第一种是老师的,第二种是我自己写的。

    功能测试

    在这里插入图片描述

  • 相关阅读:
    Java设计模式之访问者模式
    Java 编程问题:六、Java I/O 路径、文件、缓冲区、扫描和格式化
    测试开发如何设计测试用例
    全卷积神经网络概述学习记录
    微信小程序查询接口
    艾美捷QuickTiter 逆转录病毒定量试剂盒测定原理
    Java代码读取properties配置文件
    Ask Milvus Anything!聊聊被社区反复@的那些事儿ⅠⅠ
    client-go学习(6)Informer
    莞中集训游记
  • 原文地址:https://blog.csdn.net/qq_63992577/article/details/127920938