SpringBoot项目实现图片、文件上传下载功能

一、文件上传介绍

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

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

举例:

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

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

  • commons-fileupload
  • commons-io

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

PostMapping (value="/upload")
public R<String> upload (MultipartFile file){
	System.out.println(file);
	return null;
}

在这里插入图片描述

由于在页面的请求中name="file",这也是固定的,所以服务器端的MultipartFile类型的参数名也需为file。

二、文件下载

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

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

通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
由于我们图片的展示只是通过浏览器,所以选择直接在浏览器中打开的方式即可。

以下代码表示上传后img标签的src属性就会去访问imgUrl,从而访问到一个请求路径进而展示在浏览器展示图片
在这里插入图片描述

三、图片上传下载

1. 前端在线Vue方式

使用element-ui页面上传图片:只是对样式做了美化,本质上还是满足post请求,格式为Multipart,类型为file,这种方式图片的回显即视为下载,通过src发起请求。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>文件上传</title>
  <!-- 引入样式 -->
  <link rel="stylesheet" href="../../plugins/element-ui/index.css" />
</head>
<body>
   <div class="addBrand-container" id="food-add-app">
    <div class="container">
        <el-upload class="avatar-uploader"
                action="/common/upload"
                :show-file-list="false"
                :on-success="handleAvatarSuccess"
                :before-upload="beforeUpload"
                ref="upload">
            <img v-if="imageUrl" :src="imageUrl" class="avatar"></img>
            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
        </el-upload>
    </div>
  </div>
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <script src="../../plugins/vue/vue.js"></script>
    <!-- 引入组件库 -->
    <script src="../../plugins/element-ui/index.js"></script>
    <!-- 引入axios -->
    <script src="../../plugins/axios/axios.min.js"></script>
    <script src="../../js/index.js"></script>
    <script>
      new Vue({
        el: '#food-add-app',
        data() {
          return {
            imageUrl: ''
          }
        },
        methods: {
          handleAvatarSuccess (response, file, fileList) {
              this.imageUrl = `/common/download?name=${response.data}`
          },
          beforeUpload (file) {
            if(file){
              const suffix = file.name.split('.')[1]
              const size = file.size / 1024 / 1024 < 2
              if(['png','jpeg','jpg'].indexOf(suffix) < 0){
                this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')
                this.$refs.upload.clearFiles()
                return false
              }
              if(!size){
                this.$message.error('上传文件大小不能超过 2MB!')
                return false
              }
              return file
            }
          }
        }
      })
    </script>
</body>
</html>

2. 后端代码

配置文件添加文件保存路径,这个文件夹要设置成非可读模式,否则会报403错误。

reggie:
  path: D:\img\

其中的文件下载方式是通过流的形式写回图片,图片的大小一般较小,并且tomcat中已经设置了默认的文件上传大小为1MB,所以直接写回,不用提升写回速度,在文件上传下载部分会介绍分片提升文件下载速度。

package com.ldh.reggie.controller;

import com.lzk.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
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.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;

@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
    @Value("${reggie.path}")
    private String basePath;

    @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        //file是一个临时文件,需要转换到指定位置,否则本次请求完成后临时文件会删除,上传的文件也就不见了
        log.info(file.toString());

        //原始文件名,我们通过原始文件名去获取上传文件后缀,注意不要使用原始文件名作为保存名,
        // 因为不同用户可能上传同名文件,会覆盖
        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){
            e.printStackTrace();
        }
        return R.success(fileName);

    }

    @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();
            }
            //关闭资源
            outputStream.close();
            fileInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四、文件上传下载

1. 前端脚手架方式

在脚手架中引入element-ui组件:

npm i element-ui -S

在main.js文件中注册element-ui组件:

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

Vue.use(ElementUI)

前端页面::on-success表示文件上传成功后触发的事件,

<template>
  <div style="margin-left:20px">
    <el-upload
      class="upload-demo"
      action="http://localhost:8083/common/upload"
      :on-success="getAllFile"
      >
      <el-button size="small" type="primary">点击上传</el-button>
    </el-upload>

    <el-row v-for="(item,index) in fileList" :key="index">
      <el-col :span="12">
        {{ item }}  
      </el-col>
      <el-col :span="2">
        <el-button type="primary" size="small" @click="downloadFile(item)">下载</el-button> &nbsp;
        <el-button type="danger" size="small" @click="deleteFile(item)">删除</el-button>
      </el-col>
  </el-row>

  </div>

</template>
<script>
  import axios from 'axios'
  export default {
    data() {
      return {
        fileList: []
      };
    },
    methods: {
      downloadFile(item){
        window.open('http://localhost:8083/common/download/'+item)
      },
      getAllFile(){
        axios.get('http://localhost:8083/common/queryAll')
          .then((response) =>
            this.fileList = [...response.data]
          )
      },
      deleteFile(item){
        axios.get('http://localhost:8083/common/delete/'+item)
          .then((response) => {
            if(response.data){
              this.getAllFile()
              this.$message({
              message: '删除成功',
              type: 'success'})
            }else{
              this.$message.error('删除失败')
            }
          })
      }
    },
    created(){
      this.getAllFile()
    }
  }
</script>

<style scoped>
  .el-row {
    margin-bottom: 20px;
  }
</style>

2. 效果

在这里插入图片描述

3. 后端文件处理

package com.lzk.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

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


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

    private String basePath = "D:/images/";

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public String upload(MultipartFile file){
        //file是一个临时文件,需要转换到指定位置,否则本次请求完成后临时文件会删除,上传的文件也就不见了
        log.info(file.toString());

        //原始文件名,我们通过原始文件名去获取上传文件后缀,注意不要使用原始文件名作为保存名,
        // 因为不同用户可能上传同名文件,会覆盖
        String originalFilename = file.getOriginalFilename() ;
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
        String fileName = UUID.randomUUID() + "-" +originalFilename ;

        //创建一个目录对象
        File dir = new File(basePath);
        //判断目录是否存在
        if(!dir.exists()){
            dir.mkdirs();
        }
        try{
            //将临时文件转存到指定位置
            file.transferTo(new File(basePath + fileName));
        }catch (IOException e){
            e.printStackTrace();
        }
        return "文件上传成功";

    }
    
    /**
     * 分片下载文件,提升速度
     * @param name
     * @param request
     * @param response
     */
    @GetMapping("/download/{name}")
    public void download(@PathVariable("name") String name, HttpServletRequest request, HttpServletResponse response) {
        try {
            long startTime = System.currentTimeMillis();
            File file = new File(basePath + name);

            // 获取文件长度
            long fileLength = file.length();

            // 解析客户端请求的下载范围
            String rangeHeader = request.getHeader("Range");

            long start = 0;
            long end = fileLength - 1;
            if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
                String[] ranges = rangeHeader.substring(6).split("-");
                start = Long.parseLong(ranges[0]);
                if (ranges.length > 1) {
                    end = Long.parseLong(ranges[1]);
                }
            }

            // 设置响应头
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Content-Type", "application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=" + name);
            response.setHeader("Content-Length", String.valueOf(end - start + 1));
            response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);

            // 读取文件的相应部分并发送给客户端
            FileInputStream fileInputStream = new FileInputStream(file);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
            bufferedInputStream.skip(start);
            ServletOutputStream outputStream = response.getOutputStream();
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            outputStream.flush();

            // 关闭资源
            bufferedInputStream.close();
            fileInputStream.close();
            outputStream.close();
            System.out.print("耗时:");
            System.out.println(System.currentTimeMillis() - startTime);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 查询所有文件
     * @return
     */
    @GetMapping("/queryAll")
    public List<String> download(){
        // 创建 File 对象表示目录
        File dir = new File(basePath);

        List<String> fileList = new ArrayList<>();

        // 检查目录是否存在
        if (dir.exists() && dir.isDirectory()) {
            // 获取目录中的所有文件
            File[] files = dir.listFiles();

            // 遍历文件数组并打印文件名
            for (File file : files) {
                String name = file.getName();
                fileList.add(name);
            }
        }
        return fileList;
    }

    /**
     * 文件删除
     * @param name
     * @return
     */
    @GetMapping("/delete/{name}")
    public boolean download(@PathVariable(value = "name") String name ){
        // 创建 File 对象表示目录
        File file = new File(basePath + name);
        return file.delete();
    }
}

普通下载文件方法:

@GetMapping("/download/{name}")
public void download(@PathVariable("name") String name, HttpServletResponse response){
    long start = System.currentTimeMillis();
    try{
        //输入流,通过输入流读取文件内容
        FileInputStream fileInputStream = new FileInputStream(new File(basePath+name));
        //输出流,通过输出流将文件写回浏览器,在浏览器展示图片
        ServletOutputStream outputStream = response.getOutputStream();
        //下载文件:需要设置消息头
        //MIME类型,这里为二进制文件(任意文件类型都可)
        response.addHeader("content-Type","application/octet-stream");
        //attachement为附件,以附件方式获取要下载的文件,filename指定具体文件,包含了文件后缀:abc.txt
        response.addHeader("content-Disposition","attachement;filename="+name);


        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();
        System.out.print("耗时:");
        System.out.println(System.currentTimeMillis() - start);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

上面提供了两种下载请求方式:
下载一个300MB大小的文件时,普通下载耗时为:15000毫秒左右,分片方式下载速度为:3000毫秒左右。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,前端部分可以使用Vue.js框架中的axios库来发送POST请求并上传图片后端部分可以使用Spring Boot框架中的MultipartFile来处理上传的图片,并使用ResponseEntity返回下载图片。 以下是一个简单的示例代码: 前端部分: ```javascript <template> <div> <input type="file" ref="fileInput" @change="handleFileUpload"> <button @click="uploadFile">上传</button> <button @click="downloadFile">下载</button> </div> </template> <script> import axios from 'axios' export default { name: 'ImageUpload', data() { return { file: null } }, methods: { handleFileUpload(event) { this.file = event.target.files[0] }, uploadFile() { let formData = new FormData() formData.append('file', this.file) axios.post('/api/upload', formData) .then(response => { console.log(response) }) .catch(error => { console.log(error) }) }, downloadFile() { axios.get('/api/download', { responseType: 'blob' }) .then(response => { const url = window.URL.createObjectURL(new Blob([response.data])) const link = document.createElement('a') link.href = url link.setAttribute('download', 'image.jpg') document.body.appendChild(link) link.click() }) .catch(error => { console.log(error) }) } } } </script> ``` 后端部分: ```java @RestController @RequestMapping("/api") public class ImageController { @PostMapping("/upload") public ResponseEntity<?> uploadImage(@RequestParam("file") MultipartFile file) { // 处理上传的图片 return ResponseEntity.ok().build(); } @GetMapping("/download") public ResponseEntity<byte[]> downloadImage() throws IOException { // 处理下载图片 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_JPEG); headers.setContentDispositionFormData("attachment", "image.jpg"); byte[] bytes = Files.readAllBytes(Paths.get("path/to/image.jpg")); return new ResponseEntity<>(bytes, headers, HttpStatus.OK); } } ``` 需要注意的是,这只是一个简单的示例代码,实际应用中还需要添加异常处理、文件存储等相关功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值