天天看點

使用Vue和Spring Boot實作檔案上傳

推薦:​​Vue學習彙總​​

使用Vue和Spring Boot實作檔案上傳

推薦

  • ​​使用Vue和Spring Boot實作檔案下載下傳​​

檔案上傳是日常開發中很常見的業務需求,這裡部落客通過使用Vue和Spring Boot實作一個簡單的檔案上傳小例子。

前端:

<template>
    <div class="blog">
        <div class="upload">
            <input type="file" @change="fileChoose($event)">
            <button @click="submit()"><span>送出</span></button>
        </div>
    </div>
</template>
<script>
export default{
    name:'blog',
    data(){
        return {
            file:'',
        }
    },
    methods:{
        fileChoose(event){
            this.file = event.target.files[0];        //擷取檔案
            console.log(event)
        },
        submit(){
            let file = new FormData();
            file.append("file", this.file);
            this.axios.post('/file/upload',
                file ,
                {headers: {'Content-Type': 'multipart/form-data'}}
            ).then((res)=>{
                this.$message.success('檔案上傳成功');
            }).catch((res)=>{
                this.$message.error('檔案上傳失敗');
            })
        },
    }
}
</script>      

不懂​

​FormData​

​​可以自己去百度一下,​

​{headers: {'Content-Type': 'multipart/form-data'}}​

​這個需要加上。

頁面如下圖所示:

使用Vue和Spring Boot實作檔案上傳

​router:js​

​:

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router);

export default new Router({
  routes:[
    {
      path:'/',
      redirect:'/blog',
    },
    {
      path: '/blog',
      name: 'blog',
      component: () => import('./pages/blog.vue')
    }
  ]
});      

​vue.config.js​

​:

module.exports = {
  devServer:{
    host:'localhost',
    port:8080,
    proxy:{
      '/api':{
        target:'http://127.0.0.1:8081',
        changeOrigin:true,
        pathRewrite:{
          '/api':''
        }
      }
    }
  },
  lintOnSave:false,
  productionSourceMap:true
}      

後端:

​​

​pom.xml​

​:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.kaven</groupId>
    <artifactId>system</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>system</name>
    <description>系統</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>      

​application.yml​

​:

spring:
  servlet:
    multipart:
      max-file-size: 500MB
      max-request-size: 500MB

logging:
  pattern:
    console: '[%p]-%m%n'

server:
  port: 8081      

啟動類:

package com.kaven.system;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}      

​FileController​

​:

package com.kaven.system.controller;

import com.kaven.system.enums.ResponseEnum;
import com.kaven.system.vo.ResponseVo;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

@RestController
@RequestMapping("/file")
public class FileController {


    @PostMapping(value="/upload", headers="content-type=multipart/form-data")
    public ResponseVo upload(@RequestParam(value = "file") MultipartFile file) throws IOException, InterruptedException {

        System.out.println("有檔案上傳請求進來了");
        FileOutputStream fileOutputStream = null;
        InputStream inputStream = null;

        try {
            if (file != null) {
                String fileName = file.getOriginalFilename();
                if (StringUtils.isNotBlank(fileName)) {
                    java.io.File outFile = new java.io.File("E:\\"+fileName);
                    if (outFile.getParentFile() != null || !outFile.getParentFile().isDirectory()) {
                        // 建立父檔案夾
                        outFile.getParentFile().mkdirs();
                    }

                    fileOutputStream = new FileOutputStream(outFile);
                    inputStream = file.getInputStream();
                    IOUtils.copy(inputStream, fileOutputStream);
                }
            }
            else {
                return ResponseVo.error(ResponseEnum.File_NOT_EXIST);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseVo.error(ResponseEnum.ERROR);
        } finally {
            if (fileOutputStream != null) {
                fileOutputStream.flush();
                fileOutputStream.close();
            }
        }
        return ResponseVo.success(ResponseEnum.SUCCESS);
    }
}      

​ResponseVo<T>​

​:

package com.kaven.system.vo;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.kaven.system.enums.ResponseEnum;
import lombok.Data;

@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class ResponseVo<T> {

    private Integer status;

    private String msg;

    private T data;

    private ResponseVo(Integer status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    private ResponseVo(Integer status, T data) {
        this.status = status;
        this.data = data;
    }

    public static <T> ResponseVo<T> success(T data){
        return new ResponseVo<T>(ResponseEnum.SUCCESS.getCode(), data);
    }

    public static <T> ResponseVo<T> error(ResponseEnum responseEnum){
        return new ResponseVo<T>(responseEnum.getCode() , responseEnum.getDesc());
    }
}      

​ResponseEnum​

​:

package com.kaven.system.enums;

import lombok.Getter;

@Getter
public enum ResponseEnum {

    ERROR(-1 ,"服務端錯誤"),

    SUCCESS(0, "成功"),

    File_NOT_EXIST(13 , "檔案為空"),

    ;

    Integer code;

    String desc;

    ResponseEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}      

測試:

使用Vue和Spring Boot實作檔案上傳
使用Vue和Spring Boot實作檔案上傳
使用Vue和Spring Boot實作檔案上傳
使用Vue和Spring Boot實作檔案上傳

分析:

this.file = event.target.files[0];        //擷取檔案      

為什麼擷取檔案是這行代碼?

通過​

​console.log(event)​

​​,從​

​Console​

​中可以看到選擇檔案觸發的事件:

使用Vue和Spring Boot實作檔案上傳

從上圖可以知道,通過​

​srcElement​

​也可以擷取檔案,也确實如此。

<template>
    <div class="blog">
        <div class="upload">
            <input type="file" @change="fileChoose($event)">
            <button @click="submit()"><span>送出</span></button>
        </div>
    </div>
</template>
<script>
export default{
    name:'blog',
    data(){
        return {
            file:'',
        }
    },
    methods:{
        fileChoose(event){
            this.file = event.srcElement.files[0];        //擷取檔案
            console.log(event)
        },
        submit(){
            let file = new FormData();
            file.append("file", this.file);
            this.axios.post('/file/upload',
                file ,
                {headers: {'Content-Type': 'multipart/form-data'}}
            ).then((res)=>{
                this.$message.success('檔案上傳成功');
            }).catch((res)=>{
                this.$message.error('檔案上傳失敗');
            })
        },
    }
}
</script>      

但是這種方式不推薦使用。

使用Vue和Spring Boot實作檔案上傳
使用Vue和Spring Boot實作檔案上傳

由上圖可知,還可以進一步擷取檔案名稱、大小、類型。

而後端通過​

​MultipartFile​

​類型執行個體來接收檔案。

​MultipartFile​

​源碼如下:

package org.springframework.web.multipart;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;

import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;

public interface MultipartFile extends InputStreamSource {

  /**
   * Return the name of the parameter in the multipart form.
   * @return the name of the parameter (never {@code null} or empty)
   */
  String getName();

  /**
   * Return the original filename in the client's filesystem.
   * <p>This may contain path information depending on the browser used,
   * but it typically will not with any other than Opera.
   * @return the original filename, or the empty String if no file has been chosen
   * in the multipart form, or {@code null} if not defined or not available
   * @see org.apache.commons.fileupload.FileItem#getName()
   * @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename
   */
  @Nullable
  String getOriginalFilename();

  /**
   * Return the content type of the file.
   * @return the content type, or {@code null} if not defined
   * (or no file has been chosen in the multipart form)
   */
  @Nullable
  String getContentType();

  /**
   * Return whether the uploaded file is empty, that is, either no file has
   * been chosen in the multipart form or the chosen file has no content.
   */
  boolean isEmpty();

  /**
   * Return the size of the file in bytes.
   * @return the size of the file, or 0 if empty
   */
  long getSize();

  /**
   * Return the contents of the file as an array of bytes.
   * @return the contents of the file as bytes, or an empty byte array if empty
   * @throws IOException in case of access errors (if the temporary store fails)
   */
  byte[] getBytes() throws IOException;

  /**
   * Return an InputStream to read the contents of the file from.
   * <p>The user is responsible for closing the returned stream.
   * @return the contents of the file as stream, or an empty stream if empty
   * @throws IOException in case of access errors (if the temporary store fails)
   */
  @Override
  InputStream getInputStream() throws IOException;

  /**
   * Return a Resource representation of this MultipartFile. This can be used
   * as input to the {@code RestTemplate} or the {@code WebClient} to expose
   * content length and the filename along with the InputStream.
   * @return this MultipartFile adapted to the Resource contract
   * @since 5.1
   */
  default Resource getResource() {
    return new MultipartFileResource(this);
  }

  /**
   * Transfer the received file to the given destination file.
   * <p>This may either move the file in the filesystem, copy the file in the
   * filesystem, or save memory-held contents to the destination file. If the
   * destination file already exists, it will be deleted first.
   * <p>If the target file has been moved in the filesystem, this operation
   * cannot be invoked again afterwards. Therefore, call this method just once
   * in order to work with any storage mechanism.
   * <p><b>NOTE:</b> Depending on the underlying provider, temporary storage
   * may be container-dependent, including the base directory for relative
   * destinations specified here (e.g. with Servlet 3.0 multipart handling).
   * For absolute destinations, the target file may get renamed/moved from its
   * temporary location or newly copied, even if a temporary copy already exists.
   * @param dest the destination file (typically absolute)
   * @throws IOException in case of reading or writing errors
   * @throws IllegalStateException if the file has already been moved
   * in the filesystem and is not available anymore for another transfer
   * @see org.apache.commons.fileupload.FileItem#write(File)
   * @see javax.servlet.http.Part#write(String)
   */
  void transferTo(File dest) throws IOException, IllegalStateException;

  /**
   * Transfer the received file to the given destination file.
   * <p>The default implementation simply copies the file input stream.
   * @since 5.1
   * @see #getInputStream()
   * @see #transferTo(File)
   */
  default void transferTo(Path dest) throws IOException, IllegalStateException {
    FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
  }

}      

根據​

​MultipartFile​

​源碼,修改一下檔案上傳接口:

package com.kaven.system.controller;

import com.kaven.system.enums.ResponseEnum;
import com.kaven.system.vo.ResponseVo;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
@RequestMapping("/file")
public class FileController {


    @PostMapping(value="/upload", headers="content-type=multipart/form-data")
    public ResponseVo upload(@RequestParam(value = "file") MultipartFile file) throws IOException, InterruptedException {

        System.out.println("有檔案上傳請求進來了");

        try {
            if (file != null) {
                String fileName = file.getOriginalFilename();
                if (StringUtils.isNotBlank(fileName)) {
                    long start = System.currentTimeMillis();
                    Path path = Paths.get("E:/"+fileName);
                    try {
                        if(!Files.exists(path))
                            Files.createFile(path);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    file.transferTo(path);
                    long end = System.currentTimeMillis();
                    System.out.println((end - start) + "ms");
                }
            }
            else {
                return ResponseVo.error(ResponseEnum.File_NOT_EXIST);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseVo.error(ResponseEnum.ERROR);
        }
        return ResponseVo.success(ResponseEnum.SUCCESS);
    }
}      

前端分别上傳259KB、31.6MB、315MB的檔案。

使用Vue和Spring Boot實作檔案上傳

測試之前的方法:

使用Vue和Spring Boot實作檔案上傳

之前的方法耗時更少,但代碼稍微多一點點。