• 基于Springboot外卖系统13:实现文件上传下载模块


    1. 上传功能模块

    1.1 上传概述

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

    文件上传时,对页面的form表单有如下要求:

    表单属性取值说明
    methodpost必须选择post方式提交
    enctypemultipart/form-data采用multipart格式上传文件
    typefile使用input的file控件上传

    1.2 前端介绍

    1). 简单html页面表单

    1. <form method="post" action="/common/upload" enctype="multipart/form-data">
    2. <input name="myFile" type="file" />
    3. <input type="submit" value="提交" />
    4. form>

    2). ElementUI中提供的upload上传组件

    目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传。

    1.3 服务端介绍

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

    • commons-fileupload

    • commons-io

    而Spring框架在spring-web包中对文件上传进行了封装简化代码,只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件。

    1.4 下载介绍

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

    1). 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录

    2). 直接在浏览器中打开

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

    1.5 上传功能模块逻辑分析

    1.3.1 前端代码分析

    对于前端页面文件上传,可以使用ElementUI提供的上传组件,将其拷贝到项目的目录(resources/backend/page/demo)下,启动项目,访问上传页面。

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>文件上传title>
    8. <link rel="stylesheet" href="../../plugins/element-ui/index.css" />
    9. <link rel="stylesheet" href="../../styles/common.css" />
    10. <link rel="stylesheet" href="../../styles/page.css" />
    11. head>
    12. <body>
    13. <div class="addBrand-container" id="food-add-app">
    14. <div class="container">
    15. <el-upload class="avatar-uploader"
    16. action="/common/upload"
    17. :show-file-list="false"
    18. :on-success="handleAvatarSuccess"
    19. :before-upload="beforeUpload"
    20. ref="upload">
    21. <img v-if="imageUrl" :src="imageUrl" class="avatar">img>
    22. <i v-else class="el-icon-plus avatar-uploader-icon">i>
    23. el-upload>
    24. div>
    25. div>
    26. <script src="../../plugins/vue/vue.js">script>
    27. <script src="../../plugins/element-ui/index.js">script>
    28. <script src="../../plugins/axios/axios.min.js">script>
    29. <script src="../../js/index.js">script>
    30. <script>
    31. new Vue({
    32. el: '#food-add-app',
    33. data() {
    34. return {
    35. imageUrl: ''
    36. }
    37. },
    38. methods: {
    39. handleAvatarSuccess (response, file, fileList) {
    40. this.imageUrl = `/common/download?name=${response.data}`
    41. },
    42. beforeUpload (file) {
    43. if(file){
    44. const suffix = file.name.split('.')[1]
    45. const size = file.size / 1024 / 1024 < 2
    46. if(['png','jpeg','jpg'].indexOf(suffix) < 0){
    47. this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')
    48. this.$refs.upload.clearFiles()
    49. return false
    50. }
    51. if(!size){
    52. this.$message.error('上传文件大小不能超过 2MB!')
    53. return false
    54. }
    55. return file
    56. }
    57. }
    58. }
    59. })
    60. script>
    61. body>
    62. html>

    ​http://localhost:8080/backend/page/demo/upload.html​

    页面效果如下:

     在上述的浏览器抓取的网络请求中,上传文件的调用url,在哪里配置的呢,这个时候,我们需要去看一下前端上传组件。

     虽然上述是ElementUI封装的代码,但是实际上最终还通过file域上传文件,如果未指定上传文件的参数名,默认为file。

    1.6 上传服务端代码逻辑

    # 编写文件上传的方法, 通过MultipartFile类型的参数即可接收上传的文件, 方法形参的名称需要与页面的file域的name属性一致。

    1). 获取文件的原始文件名, 通过原始文件名获取文件后缀

    2). 通过UUID重新声明文件名, 文件名称重复造成文件覆盖

    3). 创建文件存放目录

    4). 将上传的临时文件转存到指定位置

    2 下载功能模块

    2.1 前端代码

    文件下载,前端页面可以使用标签展示下载的图片。

    <img v-if="imageUrl" :src="imageUrl" class="avatar">img>

    解析通过标签如何展示图片数据具体的流程:

    在文件上传成功后,在 handleAvatarSuccess 方法中获取文件上传成功之后返回的数据(文件名),然后调用 /common/download?name=xxx 进行文件的下载。在这里,如果想让上传的照片能够在页面展示出来,所以需要在服务端将文件以流的形式写回浏览器。

    2.2 下载功能模块逻辑分析

    # 在 CommonController 中定义方法download,并接收页面传递的参数name,然后读取图片文件的数据,然后以流的形式写回浏览器。

    1). 定义输入流,通过输入流读取文件内容

    2). 通过response对象,获取到输出流

    3). 通过response对象设置响应数据格式(image/jpeg)

    4). 通过输入流读取文件数据,然后通过上述的输出流写回浏览器

    5). 关闭资源

    3 上传与下载服务端代码实现

    1). application.yml (需要在application.yml中定义文件存储路径 )

    1. server:
    2. # 端口设计
    3. port: 8080
    4. spring:
    5. application:
    6. # 应用名称(可选)
    7. name: reggie_take_out
    8. datasource:
    9. # 数据集
    10. druid:
    11. driver-class-name: com.mysql.cj.jdbc.Driver
    12. url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
    13. username: root
    14. password: 123456
    15. mybatis-plus:
    16. configuration:
    17. #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    18. map-underscore-to-camel-case: true
    19. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    20. global-config:
    21. db-config:
    22. # ID生成策略:ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
    23. id-type: ASSIGN_ID
    24. reggie: # 需要在application.yml中定义文件存储路径
    25. path: D:\Java-Pro\reggie_take_out\file\

     2). CommonController

    代码实现:

    1. package com.itheima.reggie.controller;
    2. import com.itheima.reggie.common.R;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.beans.factory.annotation.Value;
    5. import org.springframework.web.bind.annotation.*;
    6. import org.springframework.web.multipart.MultipartFile;
    7. import javax.servlet.ServletOutputStream;
    8. import javax.servlet.http.HttpServletResponse;
    9. import java.io.File;
    10. import java.io.FileInputStream;
    11. import java.io.FileNotFoundException;
    12. import java.io.IOException;
    13. import java.util.UUID;
    14. /**
    15. * Description: 文件的上传和下载
    16. * @date 2022/8/17 9:55
    17. */
    18. @RestController
    19. @RequestMapping("/common")
    20. @Slf4j
    21. public class CommonController {
    22. @Value("${reggie.path}")
    23. private String basePath;
    24. @PostMapping("upload")
    25. public R upload(MultipartFile file){ // 这个名字必须和前端页面的name一样
    26. /**@Description: 文件上传
    27. * @version v1.0
    28. * @author LiBiGo
    29. * @date 2022/8/17 10:31
    30. */
    31. // file 是一个临时文件 需要转存,不然运行结束自动删除
    32. log.info(file.toString());
    33. // 原始文件名
    34. String originalFilename = file.getOriginalFilename();
    35. // 获得原文件后缀
    36. String suffix =originalFilename.substring(originalFilename.lastIndexOf("."));
    37. // 使用UUID重新生成文件名,防止文件名称重复
    38. String fileName = UUID.randomUUID().toString() + suffix;
    39. // 创建目录对象
    40. File dir = new File(basePath);
    41. // 判断当前目录是否存在
    42. if(!dir.exists()){
    43. // 目录不存在,需要创建
    44. dir.mkdir();
    45. }
    46. try {
    47. file.transferTo(new File(basePath+fileName));
    48. } catch (IOException e){
    49. e.printStackTrace();
    50. }
    51. return R.success(fileName);
    52. }
    53. @GetMapping("/download")
    54. public void download(String name , HttpServletResponse response){
    55. /**@Description: 文件下载
    56. * @version v1.0
    57. * @author LiBiGo
    58. * @date 2022/8/17 10:31
    59. */
    60. try {
    61. //输入流,通过输入流读取文件内容
    62. FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
    63. //输出流,通过输出流将文件写回浏览器
    64. ServletOutputStream outputStream = response.getOutputStream();
    65. response.setContentType("image/jpeg"); // 设置响应的文件格式,也可以不设置
    66. int len = 0;
    67. byte[] bytes = new byte[1024];
    68. while ((len = fileInputStream.read(bytes)) != -1){
    69. outputStream.write(bytes,0,len);
    70. outputStream.flush();
    71. }
    72. //关闭资源
    73. outputStream.close();
    74. fileInputStream.close();
    75. } catch (Exception e) {
    76. e.printStackTrace();
    77. }
    78. }
    79. }

  • 相关阅读:
    pyppeteer框架基本语法备忘录
    笨蛋学算法之LeetCodeHot100_3_最长连续序列(Java)
    springboot 常用注解
    医院预约挂号系统-系统结构
    const {}解构赋值
    四大类好用的科研工具分享
    Slurm集群调度策略详解(1)-主调度
    nginx 常用命令 |升级到1.20.1版本 | 如何更换 Nginx SSL 证书
    Vscode | Python | launch.json配置gevent多进程断点失效问题处理
    Ribbon负载均衡算法
  • 原文地址:https://blog.csdn.net/qq_39237205/article/details/126381569